我有以下 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
问题:
- 为什么缓冲区占用20个字节?
- 如果我声明
char buffer
而不是char buffer[1]
偏移量是 4 个字节,但我希望看到 8 个字节,因为机器是 64 位的,我认为它将使用 qword(64 位)。
提前致谢,如果问题重复,我很抱歉,我找不到答案。
最佳答案
movl %edi, -20(%rbp)
将函数 arg 从寄存器溢出到堆栈指针下方的红色区域。它有 4 个字节长,在其上方的 RSP 下方留有 16 个字节的空间。
gcc 的 -O0
(朴素的反优化)代码生成函数实际上并没有触及它为 buffer[]
保留的内存,所以你不需要不知道它在哪里。
你不能推断 buffer[]
正在用完红色区域中 a
上面的所有 16 个字节,只是 gcc 在有效打包本地人方面做得不好(因为你用 -O0
编译所以它甚至没有尝试)。但它绝对不是 20,因为没有那么多空间了。除非它把 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 $120, -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)
。 (请参阅 Godbolt 链接中的 function2
)
我还在 Godbolt 链接中包含了 clang -O0
、icc -O3
和 MSVC 优化输出。有趣的事实:ICC 选择优化掉 volatile char buffer[1] = {'x'};
而不是实际存储到内存中,但 MSVC 将其分配在影子空间中。 (Windows x64 使用不同的调用约定,返回地址上方有 32B 的阴影空间,而不是堆栈指针下方的 128B 红色区域。)
clang/LLVM -O0
选择将 a
溢出到 RSP 正下方,并将数组放在其下方 1 个字节处。
使用 just char buffer
而不是 char buffer[1]
我们从 gcc -O0
得到 movl %edi, -4(%rbp) # a, a
。它显然完全优化了未使用和未初始化的局部变量,并在保存的 RBP 正下方溢出 a
。 (我没有在 GDB 下运行它,也没有查看调试信息以查看 &buffer
是否会给我们。)
所以,您再次混淆了 a
和 buffer
。
如果我们用 char buffer = 'x'
初始化它,我们会回到旧的堆栈布局,buffer
在 -1 (%rbp)
。
或者即使我们只是让它成为 volatile char buffer;
而没有初始化器,那么它的空间存在于堆栈上并且 a
被溢出到 -20 (%rbp)
即使没有对 buffer
进行存储。
关于c - 程序集中局部变量的大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53907745/