GCC 在生成调用栈时,为什么在局部变量后留了八个字节的空白呢?

关注者
21
被浏览
4,332
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

没有你说的这种情况。

修改调用栈的是call指令。你看到的8字节空白(64位系统)可能是Function Prologue:

function_foo:
push %rbp  # 这一行要备份 %rbp,你看到的8字节空白可能就是这个值
movq %rsp,%rbp
#  好多代码
leave # leave指令会将%rbp从栈中恢复

修正:

我想了一下,你的问题应该不是那巧合的8字节,%rbp一般应该不会为空的。

而你在问题中所说的8字节也只是巧合,那只是表象。

深入查看,你会发现,GCC在函数局部变量栈的分配上,采用了16字节对齐

首先,如果你使用下面的代码:

# file: foo.c
foo() {
  int a=1,b=2,c=3,d=4,e=5;
}

编译查看生成的汇编代码:

gcc -S foo.c

你会看到生成的汇编代码是这样的(32位系统为例):

subl $32, %esp   # 本来5个int类型变量只需要20字节,但采用16字节对齐,则会为32
movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl $3, -12(%ebp)
movl $4, -8(%ebp)
movl $5, -4(%ebp)

先告诉你,使用gcc的-mpreferred-stack-boundary=n参数可以设置栈对齐字节长度为指定的2的n次方。如,你想使用4字节对齐,则使用下面的方式编译:

gcc -S foo.c -mpreferred-stack-boundary=2   # 4字节:2^2

则得到的汇编是这样的:

subl    $20, %esp

其次,为什么GCC要采用16字节对齐呢?这问题的解释就比较冗长了。

可以直接参见GCC编译参数

-mpreferred-stack-boundary

的文档。

简单来讲,CPU提供一个SSE功能(Streaming SIMD Extensions)。SSE 加入新的 8 个128Bit寄存器(XMM0~XMM7),正好每个寄存器大小为16字节。SSE功能提供给你肯定是让你用的,用了性能高,谁用谁知道。但要用的话,往XMM0~XMM7里存放数据,只能一次16字节一次16字节,所以呢,内存中数据当然要采用16字节对齐,为使用SSE作准备啦。

至于为何需要内存对齐则属于基础理论,超出此回答解释范围了。

如果你想了解GCC具体的内存对齐策略,可见这篇:

Ye, Joey - A proposal to align GCC stack