2012-10-25 53 views
2

我有一个简单的程序称为demo.c其中用于一个字符阵列的8长度堆栈C对堆栈分配的空间大小的阵列

#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 <[email protected]> 
    0x08048430 <+44>: leave 
    0x08048431 <+45>: ret  
End of assembler dump. 

0x0804840a < +6>:sub $ 0x10,%esp说,从堆栈中分配了10个字节吧?

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

+1

编译器可以完全自由地分配更多的数据,或者,因为您不使用'buffer',因此不会分配任何东西。使用'buffer'来传递函数足以阻止优化。 –

回答

5

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

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

+0

是的,这很清楚。谢谢:-)我会在几分钟内接受你的回答 –

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 

正如你可以看到我们从减去前16 ESP我们确保为使ESP指向一个16字节的地址第一(看看的and $0xfffffff0,%esp指令)。 我想编译器会尝试尊重对齐,所以他只保留16个字节。无论如何,这并不重要,因为8字节非常适合16字节。

3

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

堆栈的空间量完全依赖于编译器。在这种情况下,它最像是一个对齐问题,其中对齐方式为16字节,并且您请求了8个字符,因此它会增加到16.

如果您请求了17个字节,它很可能是sub $0x20, %esp或32字节的17.

+0

由于函数使用4B作为栈保护器canary,因此截断实际上是'main'中的'char buffer [12]'。 –

2

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

你跟-O0编译,所以GCC是告诉你一些有关的编译器内部一个超级简单的方法操作,但一些关于如何从C

好好代码

GCC是保持堆栈16B对齐每时每刻。32位SysV ABI只保证4B堆栈对齐,但GNU/Linux系统实际承担并保持gcc的默认-mpreferred-stack-boundary=4 (16B-aligned)


你的gcc也默认的版本使用-fstack-protector,所以它检查堆栈溢出函数与本地char阵列具有4个或更多个元件:

-fstack-protector
散发出额外的代码来检查用于缓冲区溢出,如堆栈粉碎攻击。这是通过将 函数与 易受攻击的对象相加来实现的。这包括调用“alloca”的函数,以及缓冲区大于8字节的函数。警卫 为 当输入一个函数时被初始化,然后在函数退出时被检查。如果警卫检查失败,则打印出错信息 ,程序退出 。

出于某种原因,这实际上是用char数组> = 4B来踢,但不是用整数数组。 (至少,当他们没有被使用时!)。指针可以替代别的任何东西,这可能与它有关。

请参阅godbolt上的代码,使用asm输出。请注意0​​是如何特殊的:它使用andl $-16, %esp来将入口上的堆栈对齐到main,但其他函数假定该堆栈在调用它们的call指令之前是16B对齐的。所以他们通常会sub $24, %esp,推后%ebp。 (%ebp和返回地址是8B总数,所以堆栈是8B远离16B对齐)。这为堆叠保护器金丝雀留下了空间。


32bit SysV ABI只需要阵列对齐其元素的自然对齐,所以对于char阵列此16B对准正是编译器决定在这种情况下做的,不是你可以指望。

64bit ABI is different

的阵列使用相同的对准作为其元素,不同的是长度的局部 或全局数组变量的至少16个字节或C99 可变长度数组变量总是具有对准的至少16个字节

(从标签wiki链接)

所以你可以指望在SysV上使用012B的对齐方式,使你可以使用SSE对齐的加载/存储。