程序集中局部变量的大小
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
问题:
- 为什么缓冲区占用20个字节?
- 如果我声明
char buffer
而不是 char buffer[1]
,则偏移量为 4 个字节,但我希望看到 8 个字节,因为机器是 64 位的,我认为它将使用 qword(64 位)。
提前致谢,如果问题重复,我很抱歉,我找不到答案。
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 -O0
、icc -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
是否会给我们。)
所以,您再次混淆了 a
和 buffer
。
如果我们用 char buffer = 'x'
初始化它,我们将回到旧的堆栈布局,buffer
在 -1(%rbp)
。
或者即使我们只是在没有初始化器的情况下使它成为 volatile char buffer;
,那么 space 因为它存在于堆栈中并且 a
被溢出到 -20(%rbp)
即使没有存储完成 buffer
.
我有以下 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 位)。
提前致谢,如果问题重复,我很抱歉,我找不到答案。
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 -O0
、icc -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
是否会给我们。)
所以,您再次混淆了 a
和 buffer
。
如果我们用 char buffer = 'x'
初始化它,我们将回到旧的堆栈布局,buffer
在 -1(%rbp)
。
或者即使我们只是在没有初始化器的情况下使它成为 volatile char buffer;
,那么 space 因为它存在于堆栈中并且 a
被溢出到 -20(%rbp)
即使没有存储完成 buffer
.