2010-10-20 30 views
3

下面的代码片段:为什么我的记忆体布局不同?

void (*foo)(); 
char X[1]; 
char Y[10]; 

可以intruitively给我一个可能的堆栈布局:

gcc -S -o stack stack.c 

然后我:

| Y[10] | 
|---------| 
| X[1] | 
|---------| 
| foo | 
|---------| 

我通过生成使用ASM文件审查这呃,推动这些变量的顺序是不同的。所以,如果我不小心做了一个X[1]我期待地址Y[0],但在实际布局中,写入X[1]的内容会覆盖分配给foo的存储器位置的第一个字节。重组是编译器优化的一个步骤,还是有人能告诉我为什么会发生这种情况?

+0

@abelenky:哎呀...谢谢你注意到:)修正了它。 – Legend 2010-10-20 16:05:38

+1

请记住,如果编译器发现它们是有益的,那么它们甚至可能不会被放置在堆栈上,它们中的一些可能被存储在寄存器中整个生命周期中。 – nos 2010-10-20 16:14:08

+0

限制变量内存布局,你应该把它们打包在一个'结构'中。在你的例子中,编译器只是按照声明的顺序将它们放到堆栈上,而堆栈则按照相反的顺序被使用。 – valdo 2010-10-20 16:30:43

回答

4

你为什么说“应该”

当然,您建议的堆栈布局应该是实现自动变量的一种特别的 - 非常明显的方式的结果,但没有什么需要它。因此,没有“应该”


给力的一些项目的顺序在内存中,这样就可以玩(行为不确定,不安全完全不可移植和!)游戏的方式覆盖,使用struct和编译器的填充#pragma秒。

+0

的事实,这是事实上的标准。自动变量存储在堆栈中。 – Andrey 2010-10-20 16:11:54

+0

更改了我的句子。感谢您指出。 – Legend 2010-10-20 16:13:06

+0

@Audrey:绝对。但是并不要求在代码中遇到空间。编译器可以很容易地将它们堆叠在一个符号表中,直到它*有*为它们腾出空间,然后通过名称或任何其他逻辑打乱作者的幻想,以字母顺序发出代码预留空间。 – dmckee 2010-10-20 16:15:27

1

可能的猜测:编译器会尝试将char数组放在一起,以尽量减少插入的填充总量。

通常,CPU在某些“整体”位对齐上检索多字节数据是最快乐的,这几乎总是对应于机器的位宽。所以一个32字节的int将对齐到一个32位的边界。为了做到这一点,编译器将用“永不访问的字节”填充堆栈。

但是,当您一次检索一个字节时,这样的对齐没有任何好处。

1
| var | address | 
|---------|---------| 
| Y[10] | x  | 
|---------|---------| 
| X[1] | x + 10 | 
|---------|---------| 
| foo | x + 11 | 
|---------|---------| 

堆栈增长降低的地址,所以如果访问下一个地址(地址更高),状阵列的下一个元素你在更大的地址访问存储器。所以X[1] = *(x + 10 + 1) = foo

+0

在* some *(刚刚流行的大多数流行的)架构上,堆栈向低地址发展。 – dmckee 2010-10-20 16:12:24

+0

@dmckee是的。这个答案在那种情况下是有效的,这也是这个问题是关于 – Andrey 2010-10-20 16:17:43

1

这是因为大多数体系结构中的stack grows down

+0

我一直以为DOWN意味着更高的地址 – Andrey 2010-10-20 16:17:07

+0

这取决于你如何看待你的记忆:)在这种情况下,“下”意味着更低的地址。 – 2010-10-20 16:23:38

1

堆栈在大多数平台上增长下来,但为什么依赖它?编译器优化可能也会将变量与4个字节边界对齐。为什么不这样做?

char x[11]; 
char *y = &x[1]; 
2

即使没有优化,内存中变量的排序通常也不是您可以指望的。无论如何,它们最终的顺序取决于你如何看待它们。如果你看到一群人从最短到最高排列,他人可能会说这些人实际上是从最高到最短。

影响这些变量在内存中的顺序的第一件事就是编译器如何实现。它有一个列表,列表可以从头到尾或从​​头到尾进行处理。所以编译器读取你的代码,产生中间代码,这个中间代码有一个需要放在堆栈上的局部变量列表。编译器并不关心它们在代码中的顺序,所以它只是以最方便的顺序查看它们。

第二件事是许多处理器使用一个颠倒的堆栈。如果你:

push A 
push B 

然后A有一个比B更大的地址,尽管B位于堆栈的顶部(并在A之上)。想象这样的一个好方法是使用C数组:

int stk[BIG]; 
int stk_top = BIG; 

然后

void stk_push(int x) { 
    stk_top--; 
    stk[stk_top] = x; 
} 

正如你可以看到stk_top指数实际上缩小为堆栈得到它更多的项目。

现在回到优化 - 编译器在重新排序不在结构中的事物时非常自由。这意味着你的编译器可以很好地对堆栈中的局部变量进行重新排序,并且在那里添加额外的填充字节来保持对齐。另外,编译器也可以自由地将一些局部变量放在堆栈中。仅仅因为你命名了一个局部变量并不意味着编译器必须在程序中真正生成它。如果一个变量没有被实际使用,它可能会被排除在程序之外。如果一个变量被大量使用,它可能被保存在一个寄存器中。如果一个变量只用于程序的一部分,那么它可能只是临时存在,并且它所使用的内存可以在该函数期间在几个其他临时变量之间共享。

+1

编译器实际上也可以自由地优化结构(它们可能不会重新排序,但它们肯定会填充它们)。他们都痴迷于调整事物。所以当你做sizeof(mystruct):)时你会很容易感到惊讶:) – Jaka 2010-10-20 16:56:23

相关问题