2

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

#include<stdio.h>


main()
{
        char buffer[8];

        return 0;
}

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

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

$ gcc -ggdb -o 演示 demo.c

然后我分析程序:

$ gdb 演示

$ 反汇编主程序

(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 <+6>: sub $0x10,%esp 说,从堆栈中分配了 10 个字节,对吗?

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

4

4 回答 4

5

不,0x10意味着它是十六进制的,即 10 16,即十进制的 16 10个字节。

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

于 2012-10-25T15:42:00.920 回答
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 字节。

于 2012-10-25T15:51:28.097 回答
3

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

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

如果您请求 17 个字节,它很可能是sub $0x20, %esp32 个字节而不是 17 个。

于 2012-10-25T15:52:04.873 回答
2

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

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

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


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

-fstack-protector
发出额外的代码来检查缓冲区溢出,例如堆栈粉碎攻击。这是通过将保护变量添加到具有易受攻击对象的函数来完成的。这包括调用“alloca”的函数,以及缓冲区大于 8 字节的函数。进入函数时初始化守卫,然后在函数退出时检查。如果保护检查失败,则会打印一条错误消息并退出程序。

出于某种原因,这实际上是使用 char 数组 >= 4B,但不是整数数组。(至少,在它们未使用时不会!)。 char指针可以给任何东西起别名,这可能与它有关。

查看Godbolt上的代码,带有 asm 输出。注意main它的特殊之处:它用于andl $-16, %esp在入口对齐堆栈到,但其他函数假定堆栈在调用它们main的指令之前是 16B 对齐的。call所以他们通常会sub $24, %esp在推后%ebp。(%ebp并且返回地址总共是 8B,所以堆栈距离 16B 对齐有 8B)。这为堆栈保护金丝雀留下了空间。


32 位 SysV ABI只要求数组与其元素的自然对齐方式对齐,因此数组的这种 16B 对齐方式正是char编译器在这种情况下决定做的事情,而不是您可以指望的事情。

64位ABI 不同

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

(来自标签 wiki 的链接)

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

于 2016-03-19T23:39:28.330 回答