我正在玩一下以更好地掌握调用约定以及如何处理堆栈,但我无法弄清楚为什么main在设置堆栈时会分配三个额外的双字(在<main+0>
)。它既没有与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.
编辑:当然我应该发布C代码:
int func(int a, int b) {
int c = 9;
return a + b + c;
}
void main() {
int x;
x = func(3, 7);
}
该平台是Arch Linux i686。
答案 0 :(得分:5)
当您输入函数时,函数的参数(包括但不限于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]
和{{1}将在argc
(或者这两个可能会被颠倒 - 取决于参数是从左到右还是从右到左推)。如果您想知道为什么我跳过[esp-48]
,那将是返回地址。
编辑:这是进入该函数的堆栈图,并在设置堆栈帧之后:
编辑2:根据您更新的问题,您所拥有的内容略微迂回,但并不是特别难以理解。进入[esp-40]
后,它不仅为main
的本地变量分配空间,而且还为您传递给main
调用的函数的参数分配空间。
这至少占据了分配的额外空间(虽然不一定全部)。
答案 1 :(得分:2)
这是一致的。我假设某些原因esp
从一开始就是一致的,但显然不是。
gcc
将堆栈帧对齐为每个默认值16个字节,这就是发生的事情。