为什么C程序堆栈没有向下增长?

时间:2012-06-24 11:50:26

标签: c stack

在gcc(Ubuntu / Linaro 4.6.3-1ubuntu5)4.6.3和Intel(R)Core(TM)2 Duo CPU上运行以下程序,我想验证c程序堆栈向下增长,我写下面的代码:

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>

    static int a = 1;
    static int b;
    int c = 2;
    int d;

    void foo(void)
    {
        int *p1;
        int *p2;
        int *p3;
        int *p4;

        printf("&p1\t%p\n", &p1);
        printf("&p2\t%p\n", &p2);
        printf("&p3\t%p\n", &p3);
        printf("&p4\t%p\n", &p4);
    }

    int main()
    {
        static int e = 3;
        static int f;
        int g = 4;
        int h;

        char *str1 = "abc";
        char *str2 = "abc";
        char *str3;
        char *str4;

        printf("&\"abc\"\t%p\n", &"abc");
        printf("&str1\t%p\n", &str1);
        printf("&str2\t%p\n", &str2);
        printf("str1\t%p\n", str1);
        printf("str2\t%p\n", str2);

        printf("&str3\t%p\n", &str3);
        printf("str3\t%p\n", str3);

        str4 = (char *)malloc(strlen("abc")*sizeof(char));
        printf("&str4\t%p\n", &str4);
        printf("str4\t%p\n", str4);

        printf("&g\t%p\n", &g);
        printf("&h\t%p\n", &h);

        foo();

        return 0;
    }

我得到了这个结果:

    &"abc"  0x8048680
    &str1   0xbff1be20
    &str2   0xbff1be24
    str1    0x8048680
    str2    0x8048680
    &str3   0xbff1be28
    str3    0x8048599
    &str4   0xbff1be2c
    str4    0x950f008
    &g  0xbff1be18
    &h  0xbff1be1c
    &p1 0xbff1bde0
    &p2 0xbff1bde4
    &p3 0xbff1bde8
    &p4 0xbff1bdec

我发现str1,str2,str3,str4的地址向上增长,p1,p2,p3,p4的地址也向上增长,而不是向下,为什么?

4 个答案:

答案 0 :(得分:5)

C标准没有说明堆栈的任何内容,更不用说它的增长方向了。

您观察到的任何行为完全取决于您的特定编译器(而后者将受到您正在运行的特定平台的影响。)

答案 1 :(得分:3)

你的程序不会测试堆栈的方向。

    int *p1;
    int *p2;
    int *p3;
    int *p4;

编译器可以按照它们在程序中出现的相反顺序来推送自动对象。

测试堆栈方向(在某些平台上确实向上)的一个很好的检查是检查两个不同函数中的自动对象的地址,一个函数调用另一个函数。

void f(void)
{
    int a = 0;
    g(&a);
}

void g(int *p)
{
    int a = 0;

    if (p - &a > 0) printf("stack goes upward\n");
    else  printf("stack goes downard\n");
}

答案 2 :(得分:2)

实际上,它确实在您的平台上向下增长,我猜是Linux + 32位x86。有两个寄存器用于在x86平台上寻址堆栈,基指针(BP)和堆栈指针(SP)。 SP会在按下值时自动递增,并在弹出时递减。在调用函数之前, callee 以相反的顺序在堆栈上推送函数,第一个参数是堆栈中最顶层的参数。

但是,在函数体中,编译器会发出将原始SP存储在BP中的代码,然后将SP增加到足以覆盖所有局部变量;这些通常按递增方向分配,并通过函数体内的BP指针进行寻址。值得注意的是,你的情况下的局部变量没有被“推”到堆栈上,因为它们是未初始化的。

答案 3 :(得分:0)

显然main中的局部变量位于堆栈中的地址高于foo中的地址,因此堆栈向下增长 - 无需担心:)

要支持编译器因变量放置的情况,请考虑使用GCC-LLVM 4.2.1在Mac OS X上运行代码时的结果:

&p1     0x7fff63a5dbf8  ^
&p2     0x7fff63a5dbf0  |
&p3     0x7fff63a5dbe8  |
&p4     0x7fff63a5dbe0  |

在Linux上使用GCC 4.2.4-1ubuntu4:

&p1     0x7fffa1e6d7b8  ^
&p2     0x7fffa1e6d7b0  |
&p3     0x7fffa1e6d7a8  |
&p4     0x7fffa1e6d7a0  |

GCC 4.4.3-4ubuntu5.1观察到相同的行为。但是随着英特尔C编译器v11.0的推出,事情发生了逆转:

&p1     0x7fff59a84c20  |
&p2     0x7fff59a84c28  |
&p3     0x7fff59a84c30  |
&p4     0x7fff59a84c38  v

两个编译器生成的汇编代码中清晰可见差异。 GCC使用基于指针(EBP / RBP)的寻址和负偏移(即相对于堆栈帧顶部的寻址),而ICC使用基于堆栈指针(ESP / RSP)的寻址和正偏移(即相对于底部的寻址)堆栈框架)。在这两种情况下,p1具有最低的绝对偏移量,p2具有下一个最低的绝对偏移量,依此类推。

GCC也可以使用基于堆栈指针的寻址(如果提供-fomit-frame-pointer选项(自动打开更高的优化级别),但至少GCC高达4.4.3仍然保留旧的变量布局,即{{1现在具有最高的偏移量,p1具有下一个最高的偏移量,依此类推。可能在较新的GCC版本中有所改变。