在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的地址也向上增长,而不是向下,为什么?
答案 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版本中有所改变。