2012-03-25 40 views
4

为了更好地掌握调用约定以及如何处理堆栈,我进行了一些尝试,但是我无法弄清楚为什么在设置堆栈时为什么主要分配三个额外的双字(在<main+0>)。它没有对齐到8字节或16字节,所以这不是我所知道的原因。正如我所看到的,主要需要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。

+1

发布C代码可能会有所帮助 – 2012-03-25 17:02:11

+0

由于您在询问有关调用约定的知识,因此该平台也很有用。例如,Mac OS X需要将堆栈保持对齐在16字节的边界上。 – 2012-03-25 17:07:21

+0

最好假定当你禁用优化时,你最终会看到未优化的代码。 – 2012-03-25 17:49:20

回答

2

它是一致的。我假设出于某种原因,esp将从一开始就对齐,这显然不是。

gcc将堆栈帧对齐默认为16字节,这就是发生了什么事。

+0

堆栈在* call调用之前由16 *对齐,所以参数16B对齐。如果堆栈在进入'main'之前没有已知的对齐方式,gcc会发出使用'和esp,-16'对齐的代码。 – 2017-12-10 17:56:13

4

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

如果,例如,main是这样的:

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

    return 0; 
} 

...我们希望看到在栈上分配的,因为我们进入主要以腾出空间给a至少35个字节。假设32位实现,通常会四舍五入到4的下一个倍数(本例中为36),以保持堆栈的32位对齐。我们不希望看到为返回值分配任何空间。 argcargv会在堆栈中,但在输入main之前它们已经在堆栈中,因此main将不必为分配空间做任何事情。

在上述情况下,为a分配空间后,a将typicaly开始在[esp-36]argv将在[esp-44]argc将在[esp-48](或这两个可能被逆转 - 这取决于是否争论被推向左边右或从右到左)。如果你想知道为什么我跳过[esp-40],那将是返回地址。

编辑:

enter image description here

编辑2:这里是在进入功能堆栈的图形,并设置堆栈帧后根据您的更新问题,你有什么是稍微的迂回,但并不特别难以理解。在输入main时,它不仅为main的本地变量分配空间,还为从main调用的功能传递参数。

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

+0

但main()只有三个本地整数,12个字节。那么它为什么分配24个字节呢? – spektre 2012-03-25 17:36:13

+1

@spektre:其余的是填充,将堆栈对齐到16个字节。尝试修改'func()'以使用less/more参数。你会注意到'main()'的堆栈区域以16字节的增长变化(随着更多的参数你最终会看到'sub esp,0x28',然后有更多的参数它会变成'sub esp,0x38' ,...)。 – ninjalj 2012-03-25 20:48:46

+0

@ninjalj 对齐是我想到的第一件事,但它没有加起来,因为我已经在进入'main()'时已经对齐了'esp'。尽管现在我想到了它,但如果是这样的话,那么对齐永远不会是必需的。 为什么它从一开始就没有对齐呢?当然在输入'main()'之前运行的代码使用堆栈,还是该代码严格地位于另一个上下文中? – spektre 2012-03-25 21:03:14