Gcc 堆栈分配,为什么会有额外的空间?

Gcc 堆栈分配,为什么会有额外的空间?,gcc,assembly,x86,stack,memory-alignment,Gcc,Assembly,X86,Stack,Memory Alignment,为了更好地掌握调用约定以及如何处理堆栈,我做了一些尝试,但我不明白为什么main在设置堆栈时会额外分配三个双字(在)。它既不与8字节对齐,也不与16字节对齐,所以据我所知,这不是原因。在我看来,main需要12个字节来表示func的两个参数和返回值 我错过了什么 该程序是在x86体系结构上用“gcc-ggdb”编译的C代码 编辑:我从gcc中删除了-O0标志,它对输出没有影响 (gdb) disas main Dump of assembler code for function main:

为了更好地掌握调用约定以及如何处理堆栈,我做了一些尝试,但我不明白为什么main在设置堆栈时会额外分配三个双字(在
)。它既不与8字节对齐,也不与16字节对齐,所以据我所知,这不是原因。在我看来,main需要12个字节来表示func的两个参数和返回值

我错过了什么

该程序是在x86体系结构上用“gcc-ggdb”编译的C代码

编辑:我从gcc中删除了-O0标志,它对输出没有影响

(gdb) disas main
Dump of assembler code for function main:
    0x080483d1 <+0>:    sub    esp,0x18
    0x080483d4 <+3>:    mov    DWORD PTR [esp+0x4],0x7
    0x080483dc <+11>:   mov    DWORD PTR [esp],0x3
    0x080483e3 <+18>:   call   0x80483b4 <func>
    0x080483e8 <+23>:   mov    DWORD PTR [esp+0x14],eax
    0x080483ec <+27>:   add    esp,0x18
    0x080483ef <+30>:   ret    
End of assembler dump.

平台是Arch Linux i686。

当您输入函数时,函数的参数(包括但不限于
main
)已经在堆栈上。在函数中分配的空间用于局部变量。对于具有简单返回类型(如
int
)的函数,返回值通常位于寄存器中(
eax
,在x86上使用典型的32位编译器)

例如,如果
main
是这样的:

int main(int argc, char **argv) { 
   char a[35];

   return 0;
}
…我们希望在进入main时在堆栈上至少分配35个字节,以便为
a
腾出空间。假设采用32位实现,则通常将其四舍五入到4的下一个倍数(本例中为36),以保持堆栈的32位对齐。我们不希望看到为返回值分配任何空间
argc
argv
将在堆栈上,但在输入
main
之前,它们已经在堆栈上,因此
main
将不必为它们分配空间

在上述情况下,在为
a
分配空间后,
a
通常从
[esp-36]
开始,
argv
将位于
[esp-44]
argc
将位于
[esp-48]
(或者这两个参数可能会颠倒,具体取决于参数是从左向右还是从右向左)。如果你想知道我为什么跳过了
[esp-40]
,那应该是回信地址

编辑:这是函数入口的堆栈图,设置堆栈框架后:

编辑2:根据你最新的问题,你的问题有点迂回,但不是特别难理解。进入
main
后,它不仅为
main
的本地变量分配空间,还为传递到从
main
调用的函数的参数分配空间


这至少占到了所分配的部分额外空间(虽然不一定是全部)。

这是一种对齐方式。出于某种原因,我认为
esp
从一开始就应该对齐,但事实显然并非如此


gcc
将堆栈帧与每个默认值的16字节对齐,这就是所发生的情况。

发布C代码可能会有所帮助平台也会很有用,因为您询问的是调用约定。例如,Mac OS X要求堆栈在16字节边界上保持对齐。最好假设当您禁用优化时,您最终会看到未优化的代码。@HansPassant当然可以,但在这种特殊情况下没有区别。我在没有它的情况下重新编译,并且反汇编是相同的。嗯,那不可能是正确的。优化后,您应该只剩下
ret
。代码没有副作用,因此可以完全消除。但是main()只有三个本地整数,12个字节。那么它为什么分配24个字节呢?@spektre:其余的是填充,将堆栈与16个字节对齐。尝试修改
func()
以使用更少/更多参数。您会注意到,
main()
的堆栈区域以16字节的增量变化(随着参数的增加,您最终会看到
sub-esp,0x28
,然后随着参数的增加,它将变为
sub-esp,0x38
,…)。@ninjalj对齐是我想到的第一件事,但是它没有加起来,因为我假设在输入
main()
时,
esp
已经对齐了。尽管现在我想起来了,如果是的话,对齐就永远不必要了。为什么不从一开始就对齐呢?当然,在进入
main()
之前运行的代码使用堆栈,还是该代码严格地位于另一个上下文或其他内容中?堆栈在
调用
指令之前按16对齐,因此arg是16B对齐的。如果堆栈在进入
main
之前没有已知的对齐方式,gcc将发出使用
和esp,-16
对齐堆栈的代码。
int main(int argc, char **argv) { 
   char a[35];

   return 0;
}