我有以下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
问题:
char buffer
而不是char buffer[1]
,则偏移量是4个字节,但是我希望看到8,因为计算机是64位的,我认为它将使用qword(64位)。在此先感谢您,如果问题重复,很抱歉,我找不到答案。
答案 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链接中包括了function2
,clang -O0
和MSVC优化输出。有趣的事实:ICC选择优化icc -O3
而不实际存储到内存中,但是MSVC在影子空间中分配它。 (Windows x64使用不同的调用约定,并且返回地址上方具有32B阴影空间,而不是堆栈指针下方的128B红色区域。)
clang / LLVM volatile char buffer[1] = {'x'};
选择将-O0
溢出到RSP的正下方,并将数组放在其下方1个字节。
a
代替char buffer
我们从char buffer[1]
获得了movl %edi, -4(%rbp) # a, a
。显然,它完全优化了未使用和未初始化的局部变量,并在保存的RBP下方溢出gcc -O0
。 (我没有在GDB下运行它,也没有查看调试信息以查看a
是否会给我们。)
因此,您再次将&buffer
与a
混合在一起。
如果我们使用buffer
对其进行初始化,我们将返回旧堆栈布局,在char buffer = 'x'
处使用buffer
。
或者即使我们只是在没有初始化程序的情况下使它-1(%rbp)
,它的空间就存在于堆栈中,并且volatile char buffer;
会溢出到a
,即使没有对{{1 }}。
答案 1 :(得分:-2)
4个字节对齐的char,8个字节推入rbp,8个字节a
= 20。a
的起始地址是当前堆栈指针减去20