C为堆栈分配堆栈空间大小

时间:2012-10-25 15:40:22

标签: c assembly gdb

我有一个名为demo.c的简单程序,它为堆栈上长度为8的char数组分配空间

#include<stdio.h>


main()
{
        char buffer[8];

        return 0;
}

我认为8个字节将从堆栈中分配给8个字符,但如果我在gdb中检查这个字节,则从堆栈中减去10个字节。

我在我的Ubuntu 32位机器上使用此命令编译程序:

$ gcc -ggdb -o demo demo.c

然后我用以下方式分析程序:

$ gdb演示

$ disassemble main

(gdb) disassemble main
Dump of assembler code for function main:
   0x08048404 <+0>: push   %ebp
   0x08048405 <+1>: mov    %esp,%ebp
   0x08048407 <+3>: and    $0xfffffff0,%esp
   0x0804840a <+6>: sub    $0x10,%esp
   0x0804840d <+9>: mov    %gs:0x14,%eax
   0x08048413 <+15>:    mov    %eax,0xc(%esp)
   0x08048417 <+19>:    xor    %eax,%eax
   0x08048419 <+21>:    mov    $0x0,%eax
   0x0804841e <+26>:    mov    0xc(%esp),%edx
   0x08048422 <+30>:    xor    %gs:0x14,%edx
   0x08048429 <+37>:    je     0x8048430 <main+44>
   0x0804842b <+39>:    call   0x8048340 <__stack_chk_fail@plt>
   0x08048430 <+44>:    leave  
   0x08048431 <+45>:    ret    
End of assembler dump.

0x0804840a&lt; + 6&gt;:sub $ 0x10,%esp说,从堆栈中分配了10个字节对吗?

为什么分配10个字节而不是8个?

4 个答案:

答案 0 :(得分:5)

不,0x10表示它是十六进制的,即10 16 ,十进制为16 10 字节。

可能是由于堆栈的对齐要求。

答案 1 :(得分:3)

请注意,常量$ 0x10是十六进制,这等于16字节。 看一下机器代码:

0x08048404 <+0>: push   %ebp
0x08048405 <+1>: mov    %esp,%ebp
0x08048407 <+3>: and    $0xfffffff0,%esp
0x0804840a <+6>: sub    $0x10,%esp
...
0x08048430 <+44>:    leave  
0x08048431 <+45>:    ret 

正如您在我们从esp中减去16之前所看到的那样,我们确保首先使esp指向一个16字节对齐的地址(请查看and $0xfffffff0,%esp指令)。 我想编译器会尝试尊重对齐,所以他只需保留16个字节。无论如何,因为8字节非常适合16字节。

答案 2 :(得分:3)

sub $0x10, %esp表示堆栈上有16个字节,而不是10个,因为0x是十六进制表示法。

堆栈的空间量完全取决于编译器。在这种情况下,它最像是一个对齐问题,其中对齐是16个字节而你已经请求了8个,所以它增加到16个。

如果您请求了17个字节,则很可能是sub $0x20, %esp或32个字节而不是17个。

答案 3 :(得分:2)

(我跳过了其他答案更详细解释的一些事情)。

你使用-O0编译,所以gcc以一种超级简单的方式运行,它告诉你一些关于编译器内部的东西,但很少关于如何从C编写好的代码。

gcc始终保持堆栈16B对齐。 32位SysV ABI仅保证4B堆栈对齐,但GNU / Linux系统实际上假定并维护gcc的默认-mpreferred-stack-boundary=4 (16B-aligned)

您的gcc版本也默认使用-fstack-protector,因此它检查具有4个或更多元素的本地char数组的函数中的堆栈粉碎:

  

-fstack-protector
     发出额外的代码来检查缓冲区溢出,例如堆栈粉碎攻击。这是通过添加一个保护变量来完成的   功能              脆弱的物体。这包括调用“alloca”的函数,以及缓冲区大于8字节的函数。守卫   是              输入功能时初始化,然后在功能退出时检查。如果防护检查失败,则会显示错误消息   印刷和              程序退出。

出于某种原因,这实际上是使用char数组&gt; = 4B而不是整数数组。 (至少,不是他们没用的时候!)。 char指针可以为任何东西添加别名,这可能与它有关。

使用asm输出查看godbolt上的代码。注意main是如何特殊的:它使用andl $-16, %esp将条目上的堆栈与main对齐,但是其他函数假设堆栈在调用的call指令之前是16B对齐的他们。因此,在推送sub $24, %esp之后,他们通常会%ebp。 (%ebp和返回地址总共为8B,因此堆栈距离16B对齐是8B。这为堆栈保护器金丝雀留出了空间。

32bit SysV ABI只需要将数组与其元素的自然对齐对齐,因此char数组的这个16B对齐正是编译器在这种情况下决定做的,而不是你的可以指望。

64bit ABI is different

  

除了本地数组外,数组使用与其元素相同的对齐方式   或全局数组变量,长度至少为16个字节或C99   变长数组变量始终具有至少16的对齐   字节

(来自标签wiki的链接)

因此,您可以指望在{SysV上{16}对齐char buf[1024],允许您在其上使用SSE对齐的加载/存储。