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;

}

 

 

窥孔优化:不管整体代码多复杂,只看局部的代码,先看一部分代码能否优化,再看下一部分,此过程会循环,优化一部分后会从头开始扫描。

 

 

乘法运算:

  1. 常量 * 常量

常量折叠

 

  1. 变量 * 2的幂

左移

 

  1. 变量 * 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的优化如下:

 

 

如果碰到无优化的运算,直接按指令意义还原代码。

 

  1. 变量 * 变量

无法优化,直接用乘法指令计算。

 

5. vs中乘法指令不论两个操作数是有符号还是无符号,都按有符号处理,用imul指令。

 

除法运算:

vs中除法运算的两个操作数都是有符号时,才按有符号处理,用idiv指令,其他都按无符号处理,用div指令。代码中要注意类型一致,防止结果和预期不符。

无符号数除以2的幂右移即可。

 

向下取整:向负无穷方向取整,math.hfloor函数有此功能。计算机的右移一位是向下取整的操作。

 

向上取整:向正无穷方向取整,math.hceil函数有此功能。

 

0取整:向0方向取整

 

a,b为整数时,有:

 

 

有符号数除以2的幂:

右移运算的结果是向下取整(对于负数,舍弃了部分表示正数的bit),

  1. 对于正数的计算结果,符合计算机的向0取整规则;
  2. 对于负数的计算结果,向下取整导致结果不符合计算机的向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,优化后的汇编代码如下,因为cdqedx已经为01,而除以2调整值也为01,因此没必要再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

 

posted @ 2020-11-17 20:01  八转达人  阅读(438)  评论(0编辑  收藏  举报