01 乘除运算入门
常量折叠:表达式参与的计算因子都是常量时,编译时求值,不产生运算指令:
4: int a = 4 * (7 - 8) / 45 * (4 + 34) + 43;
0040D748 C7 45 FC 2B 00 00 00 mov dword ptr [ebp-4],2Bh
常量传播:当一个变量初始化为常量,在其后引用这个变量的值,相当于直接引用初始化常量的值,release版会做此操作,debug版为了便于调试,可能没有此操作。
int main(int argc, char *argv[]){
int a = 4 * (7 - 8) / 45 * (4 + 34) + 43;
int b = a;
printf("%d", b);
return 0;
}
窥孔优化:不管整体代码多复杂,只看局部的代码,先看一部分代码能否优化,再看下一部分,此过程会循环,优化一部分后会从头开始扫描。
乘法运算:
- 常量 * 常量
常量折叠
- 变量 * 2的幂
左移
- 变量 * 非2的幂
乘法有3种:
Mul A
edx.eax = eax * A ;32位数相乘得到64位数
Mul A, B
A = A * B ;高位丢弃
Mul A, B, imm
A = B * imm ;高位丢弃
乘法可以优化位lea的组合,例如argc*28的优化如下:
如果碰到无优化的运算,直接按指令意义还原代码。
- 变量 * 变量
无法优化,直接用乘法指令计算。
5. vs中乘法指令不论两个操作数是有符号还是无符号,都按有符号处理,用imul指令。
除法运算:
vs中除法运算的两个操作数都是有符号时,才按有符号处理,用idiv指令,其他都按无符号处理,用div指令。代码中要注意类型一致,防止结果和预期不符。
无符号数除以2的幂右移即可。
向下取整:向负无穷方向取整,math.h中floor函数有此功能。计算机的右移一位是向下取整的操作。
向上取整:向正无穷方向取整,math.h中ceil函数有此功能。
向0取整:向0方向取整
当a,b为整数时,有:
有符号数除以2的幂:
右移运算的结果是向下取整(对于负数,舍弃了部分表示正数的bit),
- 对于正数的计算结果,符合计算机的向0取整规则;
- 对于负数的计算结果,向下取整导致结果不符合计算机的向0取整规则。所以对于负数,计算结果应该向上取整。可以按照上述公式,将上整转化为下整。
例如有符号数A,计算A/8的结果,
1.如果A是正数,直接用A>>3计算得到结果;
2.如果A是负数,用(A+8-1)/8,即(A+7)>>3得到计算结果。
运行以下代码可以看出,对于负数直接右移,结果不正确(只有在恰好整数除时才对一次):
for (int i = -50; i <= 50; ++i) {
printf("%d>>3 = %d\n", i, i >> 3);
printf("%d/8 = %d\n", i, i / 8);
printf("\n");
}
修正后的代码:
for (int i = -50; i <= 50; ++i) {
if (i >= 0) {
printf("%d>>3 = %d\n", i, i >> 3);
printf("%d/8 = %d\n", i, i / 8);
}
else {
printf("(%d+8-1) >>3 = %d\n", i, (i+8-1) >> 3);
printf("%d/8 = %d\n", i, i / 8);
}
printf("\n");
}
计算结果已经正确,下面再优化掉分支:
for (int i = -50; i <= 50; ++i) {
int n = i > 0 ? 0 : 7; //正数+0再右移,负数+7再右移
printf("(%d+%d) >>3 = %d\n", i, n, (i + n) >> 3);
printf("%d/8 = %d\n", i, i / 8);
printf("\n");
}
有符号数除以8,优化后的汇编代码如下:
mov eax, [esp+argc]
cdq ;if argc>=0 edx=0, else edx=ffffffff
and edx, 7 ;if argc>=0 edx=0, else edx=7
add eax, edx ;if argc>=0 eax=argc+0, else eax=argc+7
sar eax, 3 ;调整后的值再右移3位,相当于argc除以8
有符号数除以2的幂,都可用以上代码定式计算。
有符号数除以2,优化后的汇编代码如下,因为cdq后edx已经为0或1,而除以2调整值也为0或1,因此没必要再and了:
mov eax, [esp+argc]
cdq ;if argc>=0 edx=0, else edx=ffffffff
sub eax,edx ;if argc>=0 eax=n-0, else eax=n-(-1)
sar eax,1 ;调整后的值再右移1位,相当于argc除以2