我有一个名为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个?
答案 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对齐正是编译器在这种情况下决定做的,而不是你的可以指望。
除了本地数组外,数组使用与其元素相同的对齐方式 或全局数组变量,长度至少为16个字节或C99 变长数组变量始终具有至少16的对齐 字节
(来自x86标签wiki的链接)
因此,您可以指望在{SysV上{16}对齐char buf[1024]
,允许您在其上使用SSE对齐的加载/存储。