我正在考虑如何在x86-64汇编中编译数组。
我正在阅读“计算机系统-程序员的观点”,作者给予了以下公式:
E[i] -> `movl (%rdx, %rcx, 4i), %eax`
但我只是使用编译器资源管理器和GCC 12.2来查看以下程序的生成程序集:
int main() {
int arr[] = {3,4,5};
for (int i = 0; i < 3; i++) {
arr[i];
}
}
为此生成的代码是:
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-16], 3
mov DWORD PTR [rbp-12], 4
mov DWORD PTR [rbp-8], 5
mov DWORD PTR [rbp-4], 0
jmp .L2
.L3:
add DWORD PTR [rbp-4], 1
.L2:
cmp DWORD PTR [rbp-4], 2
jle .L3
mov eax, 0
pop rbp
ret
下面引用a stackoverflow answer的一段话,解释寄存器RBP和RSP的作用:
rbp是x86_64上的帧指针。在生成的代码中,它会获取堆栈指针(rsp)的快照,以便在对rsp进行调整(即为局部变量保留空间或将值推送到堆栈上)时,仍然可以从rbp的常量偏移量访问局部变量和函数参数。
RBP 16在这里吗?例如rbp - 16 = 0
,rbp - 12 = 4
,rbp - 8 = 8
哪种满足公式。
无论如何,你能给予我一个概念,教科书中的公式是如何与GCC生成的代码相关的吗?
1条答案
按热度按时间llew8vvj1#
因为
arr
不是volatile
,所以arr[i];
作为C语句编译为零指令,即使在调试版本中也是如此。您看到的所有asm只是固定位置的局部变量。如果您习惯于查看AT&T语法,请使用Godbolt上的输出下拉列表取消选中“Intel Syntax”。
此外,您可以通过编写一个函数来获取有用的asm,该函数接受指针arg并对其求和,例如因此,您可以启用轻度优化,而无需将数组优化为仅返回一个常量。如果您真的希望看到将数组初始值设定项存储到堆栈中的asm,而不是仅使用函数arg,则可以使用
volatile
强制它不返回常量。我们编写这个代码是为了查看asm,而不是运行它,所以不要编写main()
。(一般情况下,请参见 * How to remove "noise" from GCC/clang assembly output? *)Godbolt-带有
-Og
的gcc 12使用了您所期望的索引寻址模式;大多数其它优化级别进行指针递增。对于
-O2
这样的普通优化级别,我们会得到一个指针增量:为局部变量保留的内存量总是16字节的倍数,以保持堆栈对齐为16字节。实际上它应该是0,但它需要是16的倍数。
这是一个过度简化,但在这种情况下,
0*16 = 0
是16的倍数,它在push rbp
之后保持RSP的堆栈对齐。它使用的实际空间在红色区域,在RSP下面128字节。为局部变量保留的堆栈空间总是8的倍数,奇数或偶数取决于它做了多少次推送,因此RSP的总移动量是
16*n + 8
。在这种情况下,它不需要像RBX那样保留任何调用保留寄存器,它只推送RBP(因为
-fno-omit-frame-pointer
是-O0
的默认值)。它不支持
sub rsp, 16
,因为x86-64 SysV ABI包含一个红色区域;RSP下面的128个字节对于信号处理程序或任何异步处理它们的东西都是安全的,因此可以在没有“保留”的情况下使用。如果你想看到GCC为数组保留空间,可以使用-mno-red-zone
来阻止GCC这样做。