目的是为了可以让我更加清晰的了解C和ASM之间代码的对照,同时了解一些编译器的优化技巧,方便起见,我这里都用GCC来演示,编译统一开启O3优化,输出intel格式汇编语法gcc xxx.c -O3 -S -masm=intel -o xxx.s。
float *P;
void zero_array(){
int i;
for(i = 0; i < 10000; ++i) {
P[i] = 0.0f;
}
}
编译之后的代码如下
zero_array:
.LFB12:
.cfi_startproc
mov rdi, QWORD PTR P[rip]
mov edx, 40000
xor esi, esi
jmp memset
.cfi_endproc
这段汇编其实翻译为c代码就是memset(P, 0, 40000),因为float占4个字节,可以看到编译器把一个10000次4字节的写内存操作变成了一次memset操作。
int shift(int a) {
return a << 4;
}
汇编之后的代码
shift:
.LFB13:
.cfi_startproc
mov eax, edi
sal eax, 4
ret
.cfi_endproc
这里比较有意思的是如果我把4改为3,得到的汇编如下
shift:
.LFB13:
.cfi_startproc
lea eax, [0+rdi*8]
ret
.cfi_endproc
直接通过lea有效偏移地址来计算,这里左移3位相当于2^3 == 8。 继续,这回我把4改为1,下面应该是你脑子里面猜想的答案吧?
shift:
.LFB13:
.cfi_startproc
lea eax, [0+rdi*2]
ret
.cfi_endproc
其实,不是……正确的是下面这样
shift:
.LFB13:
.cfi_startproc
lea eax, [rdi+rdi]
ret
.cfi_endproc
这里使用rdi和rdi的一次加运算来代替rdi*2运算,众所周知寄存器的加运算的指令周期小于乘法运算。 继续,这回我把4改为0,机智的你应该可以想到会得到下面的汇编
shift:
.LFB13:
.cfi_startproc
mov eax, edi
ret
.cfi_endproc
这个例子里面我们可以看到仅仅是一个4字节数据的左移操作,编译器就对它划分了4种情况来进行汇编代码生成。
评论
<img src=x>