我试图理解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
?堆栈变量是否颠倒存储?
答案 0 :(得分:10)
堆栈组织完全未指定,并且特定于实现。实际上,它取决于很多编译器(甚至是它的版本)和优化标志。
有些变量甚至不在堆栈上(例如,因为它们只是保存在某些寄存器中,或者因为编译器通过内联,常量折叠等对它们进行优化 - 例如..)。
顺便说一句,你可以有一些假设的C实现,它不使用任何堆栈(即使我不能命名这样的实现)。要了解有关堆栈的更多信息:
熟悉您的计算机architecture&amp; instruction set(例如x86)&amp; ABI,然后......
请您的编译器显示汇编代码和/或一些中间编译器表示。如果使用GCC,请使用gcc -S -fverbose-asm
编译一些简单代码(在编译foo.s
时获取汇编代码foo.c
)并尝试多个优化级别(至少-O0
, -O1
,-O2
....)另请尝试-fdump-tree-all
选项(它会转储数百个显示源代码编译器内部表示的文件)。请注意,GCC还提供return address builtins
阅读Appel关于garbage collection can be faster than stack allocation的旧论文,并了解garbage collection技术(因为他们经常需要检查并可能更改调用堆栈帧内的一些指针)。要了解有关GC的更多信息,请阅读GC handbook。
可悲的是,我不知道在语言级别可以访问调用堆栈的低级语言(如C,D,Rust,C ++,Go,...)。这就是为C编写垃圾收集器很困难的原因(因为GC-s需要扫描调用堆栈指针)......但是请参阅Boehm's conservative GC以获得非常实用和实用的解决方案。
答案 1 :(得分:3)
现在几乎所有的处理器架构都支持堆栈操作指令(例如ARM中的LDM,STM指令)。编译器借助那些实现堆栈。在大多数情况下,当数据被压入堆栈时,堆栈指针会从堆栈中减少(向下增长)和递增数据。
因此,它取决于处理器架构和编译器如何实现堆栈。
答案 2 :(得分:1)
取决于编译器和平台。只要由程序一致地完成同一件事(在这种情况下,是编译器翻译为程序集,即机器代码),并且平台支持该程序(好的编译器会尝试优化程序集以获得“最大的效果”),则可以用多种方法完成同一件事。 ”)。
一个很好的资料,可以深入了解c的幕后知识,编译程序时会发生什么以及为什么会发生,这是一本免费的书,逆向工程入门(理解汇编语言)。丹尼斯·尤里切夫(Dennis Yurichev),the latest version can be found at his site。