堆栈上局部variables分配的顺序

看看这两个function:

void function1() { int x; int y; int z; int *ret; } void function2() { char buffer1[4]; char buffer2[4]; char buffer3[4]; int *ret; } 

如果我在gdb function1()中断,并打印variables的地址,我得到这个:

 (gdb) p &x $1 = (int *) 0xbffff380 (gdb) p &y $2 = (int *) 0xbffff384 (gdb) p &z $3 = (int *) 0xbffff388 (gdb) p &ret $4 = (int **) 0xbffff38c 

如果我在function2()做同样的事情,我得到这个:

 (gdb) p &buffer1 $1 = (char (*)[4]) 0xbffff388 (gdb) p &buffer2 $2 = (char (*)[4]) 0xbffff384 (gdb) p &buffer3 $3 = (char (*)[4]) 0xbffff380 (gdb) p &ret $4 = (int **) 0xbffff38c 

你会注意到,在这两个函数中, ret被存储在离栈顶最近的地方。 在function1() ,后面跟着zy ,最后是x 。 在function2()ret之后是buffer1 ,然后是buffer2buffer3 。 为什么存储顺序改变了? 在这两种情况下,我们使用的是相同数量的内存(4字节的int s和4字节的char数组),所以它不能成为填充的问题。 这种重新sorting有什么理由呢?另外,通过查看C代码来提前确定局部variables的sorting是否可能呢?

现在我知道C语言的ANSI规范中没有提到局部variables存储的顺序,编译器可以select它自己的顺序,但是我可以想象编译器有如何处理这一点,以及为什么这些规则是如此的解释。

作为参考,我在Mac OS 10.5.7上使用GCC 4.0.1

我不知道为什么GCC按照它的方式组织它的堆栈 (尽pipe我猜你可以破解它的源代码或本文并找出答案),但是我可以告诉你如何保证特定堆栈variables的顺序,如果由于某种原因你需要。 简单地把它们放在一个结构中:

 void function1() { struct { int x; int y; int z; int *ret; } locals; } 

如果我的记忆正确地服务我,spec保证&ret > &z > &y > &x 。 我离开了我的K&R,所以我不能引用章节和诗句。

所以,我做了一些更多的实验,这里是我发现的。 这似乎是基于每个variables是否是一个数组。 给出这个input:

 void f5() { int w; int x[1]; int *ret; int y; int z[1]; } 

我在gdb中结束了这个:

 (gdb) p &w $1 = (int *) 0xbffff4c4 (gdb) p &x $2 = (int (*)[1]) 0xbffff4c0 (gdb) p &ret $3 = (int **) 0xbffff4c8 (gdb) p &y $4 = (int *) 0xbffff4cc (gdb) p &z $5 = (int (*)[1]) 0xbffff4bc 

在这种情况下, int和指针首先被处理,最后被声明在堆栈的顶部,并首先被声明为更靠近底部。 然后,数组的处理方向相反,声明的前面是堆栈中最高的。 我相信这有一个很好的理由。 我不知道是什么

ISO C不仅没有说明堆栈中的局部variables的sorting,甚至不保证堆栈甚至存在。 标准只是谈论块内variables的范围和生命周期。

通常它与alignment问题有关。

大多数处理器在获取非处理器字alignment的数据时速度较慢。 他们必须把它拼成一团并拼接在一起。

可能发生的情况是将所有大于或等于处理器最佳alignment的对象放在一起,然后更紧密地包装可能未alignment的事物。 在你的例子中,你所有的char数组都是4个字节,但是我敢打赌,如果你把它们变成3个字节,它们仍然会在同一个地方结束。

但是如果你有四个单字节的数组,它们可能会在一个4字节的范围内,或者在四个单独的字节中alignment。

这一切都是关于处理器最简单的(转换为“最快”)。

C标准没有规定其他自动variables的布局。 但是,具体地说,为了避免疑惑,

[…]没有指定[function]参​​数存储的布局。 (C11 6.9.1p9)

可以理解的是,除了针对标准给出的less数几个要求(包括空指针不能指向任何有效的对象或函数以及聚合内的布局)之外,他对任何其他对象的存储布局也是不明确的对象。

C标准中没有提到单词“stack”; 例如C堆栈的实现是非常可能的,从堆中分配每个激活logging (尽pipe这些可能被理解为形成堆栈)。

给编译器一些余地的原因之一是效率。 然而,现在的编译器也会使用这个来安全地使用地址空间布局随机化和堆栈式的技巧来尝试使未定义行为的开发变得更加困难。 缓冲区的重新sorting是为了更有效地使用金丝雀。

我的猜测是,这与数据如何加载到寄存器有关。 也许,对于char数组,编译器可以并行执行某些function,这与内存中的位置有关,可以轻松地将数据加载到寄存器中。 尝试使用不同级别的优化进行编译,然后尝试使用int buffer1[1]

这也可能是一个安全问题?

 int main() { int array[10]; int i; for (i = 0; i <= 10; ++i) { array[i] = 0; } } 

如果数组在堆栈上比我低,那么这个代码将无限循环(因为它错误地访问数组[10],也就是i)。 通过将数组放在堆栈上,尝试访问超出堆栈末尾的内存将更有可能触及未分配的内存,并导致崩溃,而不是导致未定义的行为。

我用gcc做过一次相同的代码试验,除了用现在我不记得的特殊标志组合以外,不能使它失败。在任何情况下,它都会把数组放在远离i的位置。

有趣的是,如果在函数1中添加一个额外的int * ret2,那么在我的系统中,顺序是正确的,而对于只有3个局部variables的顺序是错误的。 我的猜测是,这是为了反映将使用的寄存器分配策略。 无论是或者是任意的。

这完全取决于编译器。 除此之外,某些过程variables可能永远不会被放置在堆栈上,因为它们可以将其整个生命花在寄存器中。