以下代码段有意访问t[4]
之后的下一个sizeof(int)字节,因此我知道此处正在进行的错误。我只是作为一个实验来看看编译器如何处理堆栈分配。
int t[5], i;
for (i = 0; i <= 5; i++) {
t[i] = 0;
}
在Windows上执行此代码时,使用移植版本的GNU C编译器,程序总是陷入无限循环。我确信这只会发生,因为t
和i
依次在堆栈上按顺序分配,而t[5]
指向与i
变量相同的地址。因此,在执行t[5] = 0
时,程序实际上将i
的值设置为零。
但是,当使用不同版本的GNU C编译器编译时,我永远不会得到无限循环。 t[5]
的地址与i
的地址不同。
我的问题是,为什么这种不同的行为?我知道你不应该假设这个结果,但不是堆栈分配应该以同样的方式发生吗?
我真正好奇的是编译器如何管理这些堆栈分配。有填充物吗?订单总是与源代码中的相同吗?显然,这与C标准无关,实现之间存在差异,甚至同一编译器的不同版本也存在差异。我很好奇,虽然在这种特殊情况下可能的结果和考虑因素是什么。
答案 0 :(得分:9)
您正在处理未定义的行为。编译器不需要按顺序布局自动变量(因为它们出现在源代码中)。其中一些可能在寄存器中,或者它们可能以不同的方式订购,例如,如果较小的偏移更便宜。
这样的要求仅存在于结构的成员中(成员之间具有任意填充)。
有填充物吗?
是的,编译器会遵守每种类型的对齐要求并相应地放置变量。
订单总是与源代码中的相同吗?
不,但这是许多漏洞依赖的东西。缓冲区溢出可能会覆盖相邻变量并影响整个程序的执行。
答案 1 :(得分:4)
另一个人说为什么从标准的角度来看这个行为,我会说你的代码编译优化和执行后可能会发生什么。
第一次:循环可能会展开并执行6次:
t[0] = 0;
t[1] = 0;
t[2] = 0;
t[3] = 0;
t[4] = 0;
t[5] = 0;
i = 6;
允许优化,这是可能发生的事情。更多:如果以后没有使用i
,则可以将其全部删除。
第二:编译器可能会将i
保留在寄存器中而不进行任何堆栈分配。
第三:它可能会在堆栈上以任何顺序放置变量。对于将变量保存在内存中(以及它们所在的位置)的顺序没有实际要求。
如何知道发生了什么?查看生成的程序集。这是了解情况发生的唯一方法。
顺便说一句:无限循环并不总是发生在Windows上。事实上,我无法逐字强制你的代码成为一个无限循环。
答案 2 :(得分:3)
访问t[5]
是未定义的行为。最后一项是t[4]
(t[0],t[1],t[2],t[3],t[4]
),没有t[5]
。
通过未定义的行为,任何事情都可能发生。它可能会给出预期的结果或完全混乱。
我的问题是,为什么会出现这种不同的行为?
正如我之前写的那样,UB你不能指望任何事情。如果你多次尝试,你甚至可以在同一台机器上得到另一个结果。