两个小时前我以为我已经完全理解了堆栈是如何工作的(至少它是如何在C中处理的)。但是我已经注意到我的程序中出现了一些意外的(对我而言)。
我们知道堆栈会朝着更低的内存地址增长(我说的是PC,在我的例子中:Intel 64位,Ubuntu)。因此,当创建新的堆栈帧时,属于该帧的对象具有与之前所有帧相比较低的内存地址。让我感到惊讶的是:帧中的对象具有更高的内存地址。这让我感到震惊,因为我认为先前声明的变量会获得更高的内存地址。
让我用C中的一个例子来说明我的意思。
#include <stdio.h>
void foo()
{
int firstVar = 1;
int secondVar = 2;
printf("firstVar is at: %p\n", &firstVar);
printf("secondVar is at: %p\n", &secondVar);
}
int main(void)
{
int mainVar = 0;
printf("mainVar is at: %p\n", &mainVar);
foo();
return 0;
}
使用gcc(-g,-ansi和-pedantic标志)编译后,输出为:
mainVar is at: 0x7ffd1ec0fadc
firstVar is at: 0x7ffd1ec0fab8
secondVar is at: 0x7ffd1ec0fabc
正如所料, mainVar 的内存地址高于 foo()堆栈帧中的内存地址。但是, firstVar 的内存地址低于 secondVar ,即使之前已声明过。查看 foo()的反汇编显示了这种行为:
0x000000000040052d <+0>: push %rbp
0x000000000040052e <+1>: mov %rsp,%rbp
0x0000000000400531 <+4>: sub $0x10,%rsp
0x0000000000400535 <+8>: movl $0x1,-0x8(%rbp)
0x000000000040053c <+15>: movl $0x2,-0x4(%rbp)
...
1放在2之前的四个字节,再次显示 firstVar 的内存地址低于 secondVar 。
我的问题是:为什么?根据我读过的所有参考书目,同一堆栈帧中的对象应该具有更高的内存地址。参考书目意味着互联网(例如这个网站)和着名的书籍。我正在使用一个非常标准的系统,所以我怀疑任何ELF或ABI奇怪的东西正在发生......
有什么想法吗?感谢您的阅读。
答案 0 :(得分:1)
您使用的是哪种编译器?编译器是非常复杂的程序。另外,他们比你更了解C ;-)(在我看来这是一件好事!) 无论如何,他们没有义务遵循您的陈述顺序。你的编译设置是什么?您是否针对速度或尺寸进行了优化?我没假设?
可能发生的事情是,因为您首先使用firstVar
(在printf
函数中,编译器决定将secondVar
置于firstVar
之上。{堆栈内存为{ {1}}(在firstVar
的堆栈内存之前再次释放)可以更快,更容易地重用,如果需要的话。
如果在函数secondVar
中交换前两行会怎样?
答案 1 :(得分:1)
根据我读过的所有参考书目,同一堆栈帧中的对象应该具有更高的内存地址,它们声明的时间越早
局部变量放置在堆栈上的顺序决不是标准化的,也不是堆栈帧本身的格式。编译器可以随意分配局部变量,因为它不会影响函数之外的任何内容。 除非将变量返回给调用者,但这不是这种情况。
一个观察结果:
gcc没有优化:
mainVar is at: 000000000022FE4C
firstVar is at: 000000000022FE0C
secondVar is at: 000000000022FE08
gcc -O3完全优化:
mainVar is at: 000000000022FE4C
firstVar is at: 000000000022FE08
secondVar is at: 000000000022FE0C
无论出于何种原因,优化器认为改变分配这两个变量的顺序会有好处。要知道原因,您必须详细研究特定编译器的优化器。这是一种温和有用的知识。
你在这里看不到的是,优化器可能会喜欢将这些变量放在CPU寄存器中。但是没有办法,因为你打印他们的地址和寄存器变量没有地址。通过使用变量的地址,您将强制它在堆栈上分配。
因此,这里要学到的唯一重要的事情是,您不应该编写依赖于堆栈帧的内存布局的代码,也不应该对C标准无法保证的内存布局做任何假设。
如果您需要特定订单,则需要在编译器的喉咙下显示C标准:
typedef struct
{
int firstVar;
int secondVar;
} reorder_this_if_you_can;
void foo()
{
reorder_this_if_you_can re;
printf("firstVar is at:\t %p\n", &re.firstVar);
printf("secondVar is at: %p\n", &re.secondVar);
}
现在,无论优化程度如何,订单都会突然得到保证。