【问题标题】:Are C stack variables stored in reverse?C堆栈变量是否反向存储?
【发布时间】:2015-04-18 00:02:24
【问题描述】:

我试图了解 C 如何在堆栈上分配内存。我一直认为堆栈上的变量可以描述为结构成员变量,它们在堆栈中占据连续的、连续的字节块。为了帮助说明我在某处发现的这个问题,我创建了一个重现该现象的小程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void function(int  *i) {
    int *_prev_int =  (int *) ((long unsigned int) i -  sizeof(int))  ;
    printf("%d\n", *_prev_int );    
}

void main(void) 
{
    int x = 152;
    int y = 234;
    function(&y);
}

看看我在做什么?假设 sizeof(int) 为 4:我正在查找传递的指针后面的 4 个字节,因为这将读取 调用者的堆栈中 int y 之前的 4 个字节。

它没有打印 152。奇怪的是,当我查看接下来的 4 个字节时:

int *_prev_int =  (int *) ((long unsigned int) i +  sizeof(int))  ;

现在它可以工作了,打印调用者堆栈内x 中的任何内容。为什么x 的地址比y 低?堆栈变量是否倒置存储?

【问题讨论】:

  • 我认为它是实现定义/或未指定。检查这个答案stackoverflow.com/a/4105123/1673391
  • 这完全依赖于平台,但是很多主流平台实际上是从代码/数据部分向上增长堆,从(可用)内存的顶部向下增长堆栈。你应该从不在你的 C 代码中依赖它,但是......
  • int* prev = i - 1; 太简单了不能达到同样的效果吗?
  • C 不分配任何东西。这是一个规范。它完全没有说明您要检查的内容。这意味着您不能以任何方式依赖您从实验中获得的结果。或者确实赋予它们任何意义。
  • 要了解为什么您的实验没有意义,请查看here

标签: c pointers stack memory-address


【解决方案1】:

取决于编译器和平台。只要程序始终如一地完成相同的事情(在这种情况下,编译器翻译成汇编,即机器代码)并且平台支持它(好的编译器尝试优化汇编以获得“最”的每个平台)。

深入了解 c 的幕后发生的事情、编译程序时会发生什么以及为什么会发生这些情况的一个很好的资源是免费书籍 Reverse Engineering for Beginners (Understanding Assembly Language)丹尼斯·尤里切夫,the latest version can be found at his site

【讨论】:

    【解决方案2】:

    现在几乎所有的处理器架构都支持堆栈操作指令(例如 ARM 中的 LDM、STM 指令)。编译器借助这些实现堆栈。大多数情况下,当数据入栈时,栈指针递减(向下增长),当数据从栈中弹出时递增。

    所以这取决于处理器架构和编译器如何实现堆栈。

    【讨论】:

    • 这在很大程度上取决于语言。 Go 分配堆来管理它的 stack 变量。换句话说,在 Go 程序中 stack 指针增加的可能性更大。
    • @AlexisWilke 是的,但问题是针对“C”
    • 好点子,虽然 C 实现可以选择轻松完成。
    【解决方案3】:

    堆栈组织完全未指定并且是特定于实现的。在实践中,它取决于很多编译器(甚至是它的版本)和优化标志。

    一些变量甚至不在堆栈上(例如,因为它们只是保存在一些寄存器中,或者因为编译器优化了它们 - 例如通过内联、常量折叠等)。

    顺便说一句,您可能有一些不使用任何堆栈的假设 C 实现(即使我无法命名这种实现)。

    要了解有关堆栈的更多信息:

    遗憾的是,我不知道在语言级别可以访问调用堆栈的低级语言(如 C、D、Rust、C++、Go 等)。这就是为什么为 C 编写垃圾收集器很困难的原因(因为 GC-s 需要扫描调用堆栈指针)...但是请参阅 Boehm's conservative GC 以获得非常实用和务实的解决方案。

    【讨论】:

    • 这种断言/cmets需要引用标准
    • AFAIK,C99 标准没有以规范的方式提及/任何堆栈。
    • @GrijeshChauhan:不,它不需要标准中的引号,因为标准未指定。这就是为什么你不应该依赖它的行为。
    • @GrijeshChauhan:由于您在那里有一个文本文档,因此搜索“stack”并查看它如何没有给出一个匹配项。标准未定义参数和局部变量的存储位置——即未指定的行为。也许您的平台或编译器记录了它的工作方式,这意味着它是实现定义的
    • @GrijeshChauhan:实际上,我认为添加此类信息弊大于利,因为太多的程序员不理解依赖未指定/实现定义的行为的含义,并且乐于使用“对我有用的任何东西” ",然后想知道为什么下一个编译器或操作系统更新会破坏他们的代码。
    猜你喜欢
    • 1970-01-01
    • 2017-11-05
    • 2018-04-10
    • 2017-10-06
    • 2014-02-17
    • 2015-03-09
    • 1970-01-01
    • 2011-04-13
    相关资源
    最近更新 更多