汇编中局部变量的大小

时间:2018-12-23 22:50:40

标签: c gcc assembly x86-64 word-size

我有以下C函数:

void function(int a) {
  char buffer[1];
}

它产生以下汇编代码(优化为0的gcc,64位计算机):

function:
  pushq %rbp
  movq  %rsp, %rbp
  movl  %edi, -20(%rbp)
  nop
  popq  %rbp
  ret

问题:

  • 为什么缓冲区占用20个字节?
  • 如果我声明的是char buffer而不是char buffer[1],则偏移量是4个字节,但是我希望看到8,因为计算机是64位的,我认为它将使用qword(64位)。
  • li>

在此先感谢您,如果问题重复,很抱歉,我找不到答案。

2 个答案:

答案 0 :(得分:6)

movl %edi, -20(%rbp)正在将函数arg从寄存器中溢出到堆栈指针下方的红色区域中。它的长度为4个字节,在RSP之下的上方留有16个字节的空间。

gcc的-O0(天真抗优化)代码源为您提供的功能实际上并没有触及为buffer[]保留的内存,因此您不知道它在哪里。

您不能推断出buffer[]在红色区域中的a上方的所有16个字节都已用完,只是gcc不能有效地打包本地人(因为您使用{{1 }},所以它甚至都没有尝试)。但是绝对不是20,因为空间不多了。除非将-O0 放在 buffer[]以下,否则它将保留在128字节红色区域的其余部分。 (提示:没。)


如果我们为数组添加一个初始化程序,我们可以看到它实际存储字节的位置。

a

由gcc8.2 void function(int a) { volatile char buffer[1] = {'x'}; } on the Godbolt compiler explorer编译:

-xc -O0 -fverbose-asm -Wall

所以function: pushq %rbp movq %rsp, %rbp # function prologue, creating a traditional stack frame movl %edi, -20(%rbp) # a, a movb $120, -1(%rbp) #, buffer nop # totally useless, IDK what this is for popq %rbp # tear down the stack frame ret 实际上是一个字节长,在保存的RBP值下方

对于长度至少为16个字节的自动存储阵列,x86-64 System V ABI需要16个字节的对齐方式,但这不是这种情况,因此该规则不适用。

我不知道为什么gcc在溢出的寄存器arg之前留下了多余的填充; gcc经常会错过这种优化。它没有给buffer[]任何特殊的对齐方式。

如果添加额外的本地数组,它们将在溢出的arg上方填充16个字节,仍然将其溢出到a。 (请参见Godbolt链接中的-20(%rbp)

我还在Godbolt链接中包括了function2clang -O0和MSVC优化输出。有趣的事实:ICC选择优化icc -O3而不实际存储到内存中,但是MSVC在影子空间中分配它。 (Windows x64使用不同的调用约定,并且返回地址上方具有32B阴影空间,而不是堆栈指针下方的128B红色区域。)

clang / LLVM volatile char buffer[1] = {'x'};选择将-O0溢出到RSP的正下方,并将数组放在其下方1个字节。


使用 just a代替char buffer

我们从char buffer[1]获得了movl %edi, -4(%rbp) # a, a。显然,它完全优化了未使用和未初始化的局部变量,并在保存的RBP下方溢出gcc -O0。 (我没有在GDB下运行它,也没有查看调试信息以查看a是否会给我们。)

因此,您再次将&buffera混合在一起。

如果我们使用buffer对其进行初始化,我们将返回旧堆栈布局,在char buffer = 'x'处使用buffer

或者即使我们只是在没有初始化程序的情况下使它-1(%rbp),它的空间就存在于堆栈中,并且volatile char buffer;会溢出到a,即使没有对{{1 }}。

答案 1 :(得分:-2)

4个字节对齐的char,8个字节推入rbp,8个字节a = 20。a的起始地址是当前堆栈指针减去20