程序集中局部变量的大小

Size of local variable in assembly

我有以下 C 函数:

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

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

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

问题:

提前致谢,如果问题重复,我很抱歉,我找不到答案。

4 bytes aligned char ,8 bytes pushed rbp, 8 bytes a = 20. a 的起始地址是当前堆栈指针减去 20

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

gcc 的 -O0(朴素的反优化)代码生成函数实际上并没有触及它为 buffer[] 保留的内存,所以你不知道它在哪里。

你不能推断 buffer[] 在红色区域用完了 a 以上的所有 16 个字节,只是 gcc 在有效打包局部变量方面做得不好(因为你用 -O0 所以它甚至没有尝试)。但肯定是 而不是 20,因为 space 所剩无几。除非它把 buffer[] 放在 a 下面,在 128 字节红色区域的其余部分的其他地方。 (提示:它没有。)


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

void function(int a) {
  volatile char buffer[1] = {'x'};
}

由gcc8.2编译-xc -O0 -fverbose-asm -Wall on the Godbolt compiler explorer:

function:
    pushq   %rbp
    movq    %rsp, %rbp               # function prologue, creating a traditional stack frame

    movl    %edi, -20(%rbp) # a, a

    movb    0, -1(%rbp)  #, buffer

    nop                             # totally useless, IDK what this is for
    popq    %rbp                    # tear down the stack frame
    ret     

所以 buffer[] 实际上是一个字节长, 低于保存的 RBP 值。

x86-64 System V ABI 要求对至少 16 字节长的自动存储阵列进行 16 字节对齐,但此处情况并非如此,因此该规则不适用。

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

如果您添加额外的本地数组,它们将填满溢出 arg 上方的 16 个字节,仍然溢出到 -20(%rbp)。 (参见神箭link中的function2

我还在 Godbolt link 中包含 clang -O0icc -O3 和 MSVC 优化输出。有趣的事实:ICC 选择优化掉 volatile char buffer[1] = {'x'}; 而没有实际存储到内存中,但 MSVC 将其分配在影子 space 中。 (Windows x64 使用不同的调用约定,并且在 return 地址上方有 32B 阴影 space 而不是堆栈指针下方的 128B 红色区域。)

clang/LLVM -O0 选择将 a 溢出到 RSP 正下方,并将数组放在其下方 1 个字节。


只是char buffer而不是char buffer[1]

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

所以,您再次混淆了 abuffer

如果我们用 char buffer = 'x' 初始化它,我们将回到旧的堆栈布局,buffer-1(%rbp)

或者即使我们只是在没有初始化器的情况下使它成为 volatile char buffer;,那么 space 因为它存在于堆栈中并且 a 被溢出到 -20(%rbp) 即使没有存储完成 buffer.