2011-05-21 18 views
8

我对堆栈增长检测的问题阅读了不同的回应,并且我明白,在现代架构中,堆栈可能会随机增长,可能会在堆外创建,等等。为什么函数调用而不是变量地址用于检测栈的增长方向?

但是,在这个经典的面试问题中,我想了解为什么人使用函数调用而不是比较同一函数中的2个局部变量。我认为这样做肯定有一些特殊的原因,但不是C /低级别开发人员[Java :)],我只是猜测。

这里是我试过的代码:

void sub (int *a) { 
    int b; 
    int c; 
    printf ("a:%d\n", a); 
    printf ("b:%d\n", &b); 
    printf ("c:%d\n", &c); 
    if (&b > a) { 
     printf ("Stack grows up.\n"); 
    } else { 
     printf ("Stack grows down.\n"); 
    } 
} 

int main (void) { 
    int a; 
    int b; 
    sub (&a); 
    printf ("\nHere we go again!!\n"); 
    if (&b > &a) { 
     printf ("Stack grows up.\n"); 
    } else { 
     printf ("Stack grows down.\n"); 
    } 
    return 0; 
} 

我还发现这篇文章它试图优化,我不明白是溶液:http://www.devx.com/tips/Tip/37412

PS:从这个和不同的反应其他线程,似乎问题本身是有缺陷的/不相关的,作为一个面试问题,它可能会重新强制执行错误的假设,除非有人研究了答案!

谢谢!

+0

看到这个答案。我已经详细回答了。 http://stackoverflow.com/a/17083372/1292348 – Megharaj 2013-06-13 09:17:10

回答

4

单个堆栈帧时,编译器是免费其认为合适订购的局部变量,因此代码:

int i; 
double j; 

之前或之后ji。只要编译生成正确的代码来访问变量,它可以到任何地方。实际上,除非你使用地址 - 运算符&(或者必须得到地址),否则变量可能永远不会在栈上。在通话期间它可以存储在寄存器中。

但是,堆栈帧本身的放置顺序受到限制,因为如果它们出现故障,函数返回将无法很好地工作(说得很温和)。


我应该提到,当然,前提是堆栈增长的方向仅在一个场景中非常有限是有用的。绝大多数代码不应该关心它。如果您对不同的体系结构以及它们如何处理堆栈感兴趣,请参阅this answer

+0

你在我的例子中说,因为我不使用&它可能不在堆栈上。隐含&的含义是什么? – codeObserver 2011-05-21 02:02:53

+0

是的,它可能根本不在堆栈中,它可能被放入寄存器。 – paxdiablo 2011-05-21 02:08:48

6

您不能完全控制编译器选择分配局部变量的顺序。然而,您可以合理地控制将调用的函数以及以何种顺序。

+0

不会做这样的事'int a = 0; int b =&a;'强制排序? (我正在把变量的地址去掉它被优化掉的可能性。) – RedX 2011-05-21 02:10:44

+0

@RedX:完全没有。初始化的顺序与堆栈上分配的空间的相对位置无关。 – 2011-05-21 02:20:16

+0

如果您从不使用该地址进行任何操作,同样考虑'a'的地址不会使其不能被优化。顺便说一句,'int b =&a;'是无效的C,并且不会被编译。你需要演员。 – 2011-05-21 02:20:55

5

声明变量放置在堆栈上的顺序是未定义的,但在由函数调用的函数中,内部函数调用的参数必须比外部函数晚推入堆栈。

3

的问题是,当你这样做:

void test(void) 
{ 
    int a; 
    int b; 
    if (&a < &b) 
     ... 

你无关与堆栈生长方向的结果。您知道堆叠增长方式的唯一方法是创建新的。编译器可以自由地将a放在b以上,或者a放在b以下,因为它认为合适。不同的编译器可能会有不同的结果。

但是,如果您调用另一个函数,该函数的局部变量已将置于新的堆栈帧中,即处于从调用者变量增长的方向。

+0

最后一句/段落不正确。编译器可以内联函数,在这种情况下上述问题仍然适用。确保编译器*必须*创建新的堆栈帧实际上非常困难。我能想到的最好的方法是使用函数指针,并通过将它的字节与通过库调用获得的值相比较来破坏函数指针的表示形式,该调用必须为零,但编译器无法证明它会为零。 – 2011-05-21 02:02:21

+0

@R正确,但很容易阻止编译器内联函数。使函数递归是一种方法,使用函数属性是另一种方式,将它们定义在单独的翻译单元中是第三种。 (这些都不能在所有编译器上工作,但仍然如此。) – 2011-05-21 03:36:06

4

编译器可以和做重新排序变量堆栈帧:

#include <stdio.h> 

int main() 
{ 
    char c1; 
    int a; 
    char c2; 
    int b; 

    printf("%p %p %p %p\n", &c1, &a, &c2, &b); 
    return 0; 
} 

打印

0x7ffff62acc1f 0x7ffff62acc18 0x7ffff62acc1e 0x7ffff62acc14 

这里(使用在64位的Linux的gcc 4.4.3)。 c2已移至c1旁边。

+0

+1来证明它。好奇你怎么确定这会完成?所有类似数据类型的文字都被分组在一起进行一些优化? – codeObserver 2011-05-21 02:09:54

+1

@ p1。我不确定这是否会完成,但我已经花了足够的时间重新排序struct字段以减少内存消耗,以了解如果要尊重对齐约束并最小化堆栈帧的大小,则必须重新排序。 – AProgrammer 2011-05-21 02:14:22

相关问题