我编译了以下C代码:
typedef struct {
long x, y, z;
} Foo;
long Bar(Foo *f, long i)
{
return f[i].x + f[i].y + f[i].z;
}
使用命令gcc -S -O3 test.c
。这是输出中的Bar函数:
.section __TEXT,__text,regular,pure_instructions
.globl _Bar
.align 4, 0x90
_Bar:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
leaq (%rsi,%rsi,2), %rcx
movq 8(%rdi,%rcx,8), %rax
addq (%rdi,%rcx,8), %rax
addq 16(%rdi,%rcx,8), %rax
popq %rbp
ret
Leh_func_end1:
我对这个汇编代码有几个问题:
pushq %rbp
”,“movq %rsp, %rbp
”和“popq %rbp
”的目的是什么,如果rbp
和rsp
都未使用身体的功能?rsi
和rdi
会自动包含C函数(i
和f
的参数)而不从堆栈中读取它们?我尝试将Foo的大小增加到88个字节(11 long
s),leaq
指令变为imulq
。将我的结构设计为具有“圆形”尺寸以避免乘法指令(为了优化阵列访问)是否有意义? leaq
指令已替换为:
imulq $88, %rsi, %rcx
答案 0 :(得分:7)
该函数只是使用这些指令构建自己的堆栈帧。他们并没有什么不寻常之处。但是,您应该注意,由于此函数的小尺寸,在代码中使用时可能会内联。但是,编译器总是需要生成函数的“正常”版本。另外,@ ouah在他的回答中说了什么。
这是因为AMD64 ABI指定的参数应该传递给函数。
如果类是INTEGER,则序列的下一个可用寄存器 使用%rdi,%rsi,%rdx,%rcx,%r8和%r9。
第20页,AMD64 ABI草案0.99.5 - 2010年9月3日
这不是直接与结构大小相关,而是 - 函数必须访问的绝对地址。如果结构的大小是24个字节,f
是包含结构的数组的地址,i
是必须访问数组的索引,那么每个结构的字节偏移量是i*24
。在这种情况下乘以24是通过lea
和SIB寻址的组合实现的。第一个lea
指令只计算i*3
,然后每个后续指令使用i*3
并将其进一步乘以8,从而以所需的绝对字节偏移量访问数组,然后使用立即位移访问各个结构成员((%rdi,%rcx,8)
。8(%rdi,%rcx,8)
和16(%rdi,%rcx,8)
)。如果你将结构的大小设置为88字节,那么使用lea
和任何类型的寻址组合都无法快速完成这样的事情。编译器只是假设一个简单的imull
在计算i*88
时比一系列移位,添加,lea
或其他任何内容更有效。
答案 1 :(得分:2)
- 如果在函数体中没有使用rbp和rsp,pushq%rbp,movq%rsp,%rbp和popq%rbp的目的是什么?
醇>
使用调试器时跟踪帧。添加-fomit-frame-pointer
进行优化(请注意,应该在-O3
启用它,但在我使用它的很多gcc
版本中不是这样。)
答案 2 :(得分:0)
3. I tried increasing the size of Foo to 88 bytes (11 longs) and the leaq instruction became an imulq. Would it make sense to design my structs to have "rounder" sizes to avoid the multiply instructions (in order to optimize array access)?
leaq调用(基本上和在此cae中)计算k * a + b,其中“k”是1,2,4或8,“a”和“b”是寄存器。如果“a”和“b”相同,它可以用于1,2,3,4,5,8和9个长的结构。
16个longs 的较大结构可以通过计算“k”和加倍的偏移来优化,但我不知道这是否是编译器实际会做的;你必须测试。