大理寺少卿 发表于 3 天前

X86C++反汇编05.除法优化-取模 和三目运算

高低版本区别比较大

### 取模

### **取模 就是 jns 或者 有 a-qb 的或者有绝对值了3种情况**

#### 当模值(除数)为变量时,无优化

```
int main(int argc, char* argv[])
{
   printf("%d\r\n",3 % argc );
   return 0;
}
反汇编代码:
mov   eax, 3
cdq
idiv   
push    edx
push    offset aD       ; "%d\r\n"
call    sub_401020
add   esp, 8
```

#### 无符号数 模2的整数次 幂

debug 版 没优化 release 会优化

```
int main(unsignedint argc, char* argv[])
{
   printf("%d\r\n",argc % 2 );
   return 0;
}
反汇编代码:
mov   eax,
and   eax, 1   ;取最低位
push    eax
push    offset aD       ; "%d\r\n"
call    sub_401020
分析:   取 %2 的值 其实没必要做除法,取最低位的值 其实就可以了,最低位是0,那么余数就是0,最低位是1,那么余数就是 1

同理推断:2^2 就取 最低2位就够了, 2^n取最低n 位 就够了
int main(unsignedint argc, char* argv[])
{
   printf("%d\r\n",argc % 8);
   return 0;
}
反汇编代码:
mov   eax,
and   eax, 7      ;8 = 2^3   取低3位111b= 7
push    eax
push    offset aD       ; "%d\r\n"
call    sub_401020

注意: 上式 1 必须连续 ,否则 就是 and 不是取模(即 1,3,7,.........., 2^(n-1) )

定式:   当 n 为 2的整数次幂 的常量是
argc%n的 反汇编代码为

mov   eax,
and   eax, n - 1   
push    eax


```

#### 无符号数模非2的整数次 幂

低版本无优化,高版本有优化

```
int main(unsignedint argc, char* argv[])
{
   printf("%d\r\n",argc % 7 );
   return 0;
}
低版本(VC6.0) 汇编代码
mov   eax,
xor       edx, edx
mov   ecx, 7
div      ecx
push    edx
push    offset aD       ; "%d\r\n"
call       sub_401020

高版本(VS2019)
int main(int argc, char* argv[])
{
   printf("%d\r\n",argc % 7 );
   return 0;
}
反汇编代码:
mov   esi,
mov   eax, 92492493h
imul    esi
add   edx, esi
sar   edx, 2
mov   ecx, edx
shr   ecx, 1Fh
add   ecx, edx
; 获取 q,      a / 7   

lea   eax, ds:0
sub   eax, ecx
;qb,   q * 7

sub   esi, eax
; a - qb,   a - (a/b * b)

push    esi
push    offset _Format; "%d\r\n"
call    _printf


分析 :
a /b=q ..r
r = a - qb



```

#### 有符号数 模2的整数次 幂

debug版 和 release 版都会优化

```
int main(int argc, char* argv[])
{
   printf("%d\r\n",argc % 8 );
   return 0;
}

               mov   eax,
               and   eax, 80000007h      ;保留符号和低3位
               jns   short loc_401037    ;如果是负数,中间需要填1,如果模是0,直接跳走
               dec   eax
               or      eax, 0FFFFFFF8h   ;低3位不变 , 中间填1
               inc   eax
loc_401037:                        
                push    eax
               push    offset Format   ; "%d\r\n"
               call    _printf

分析:中间的dec和 inc 看着是没什么用,实际上是处理整除的情况(清掉高位的1)

无分支情况:
int main(int argc, char* argv[])
{
   printf("%d\r\n",argc % -8 );
   return 0;
}
反汇编代码:
;求绝对值,相当于b变成无符号取模再回填符号
mov   eax,
cdq
xor   eax, edx
sub   eax, edx
;无符号取模
and   eax, 7
;还原符号位
xor   eax, edx       ;此时 edx仍然保留 eax 的符号位
sub   eax, edx

push    eax
push    offset aD       ; "%d\r\n"
call    sub_401030

分析:    余数的符号跟被除数相关,跟除数无关
当 B <0, B = 2^n
A % B   =>|A| % |B|    结果在回填 A 的符号
andA,n-1   ;无符号取模
xor   eax, edx       ;回填A 的符号
sub   eax, edx
```

**无分支求绝对值**

**mov   eax   ,n**

**cdq**

**xor      eax    ,edx**

**sub   eax   ,edx**

分析:

当 n 为正数时   dex 为 0,异或,减0,eax 的值不变

当 n 为父数时   dex 为 -1 (FFFFFFFF) , eax 异或   (FFFFFFFF)等于取反   , eax 减 dex等于 +1就相当于对eax求补

### 三目运算(条件表达式)

#### 基本原型

int main(int argc, char* argv[])

{

```
printf("%d\r\n", argc == 0 ? 0 : -1 );
```


```
return 0;
```


}

!(./notesimg/1657526284110-da8b13b6-018b-4d62-b102-2b0db9f4cb41.png)

当表达式 2 或 3 不为常量,没有优化空间

反汇编代码:

mov   eax,

**neg**   eax                   ;=>0 -eax    只有当eax 等于 0 是cf 才不产生借位

**sbb**   eax, eax         ;sbb    A,B   =>   A   =    A - B - CF

push    eax

push    offset Format   ; "%d\r\n"

call    _printf

分析:    neg    sbb 是一个很经典的组合,间接造一个 0 和 -1出来,便于后继的位运算

当eax =0   时 , **neg**   eax   ->   cf =0      =>    **sbb**   eax, eax->   eax = eax - eax - cf = 0

当eax !=0 时 ,**neg**   eax   ->   cf =1   =>    **sbb**   eax, eax->   eax = eax - eax - cf = -1

注意:如果 sbb 上面是 cmp , 也会产生 neg 一样的效果

```
int main(int argc, char* argv[])
{
    printf("%d\r\n", argc == 0 ? 32 : 48 );
    return 0;
}
反汇编代码:
mov   eax ,argc
neg    eax
sbb    eax ,eax
add    eax, 48-32
add    eax,32
分析 :
当   argc =0;   到 sbb 之后   eax = 0;         add    eax, 48-32=>eax = 0   
当   argc !=0;   到 sbb 之后eax = -1;         add    eax, 48-32=>eax = 16
```

**三目运算无分支定式**

A ==0 ? B : C

movreg,   A

neg    reg

sub    reg,   reg

and    reg,   C - B

add    B

如果表达式不等      A !=0 ? B : C

可以换成等式          A ==0 ? C : B

如果   是A == n ?B : C可以在neg 之前 加一条    subreg, n

#### setxx 指令

经过   cmp/test / sub / add 等处理(影响标志)之后, 后面出现了   setxx   r8指令(设置8位寄存器的值为0或1)


|      | 有符号 | 无符号 |
| ------ | -------- | -------- |
| 大于 | Greate | Above|
| 小于 | Less   | Blow   |

```
cmpeax,100
setgecl
上式意思是   eax跟100比较   如果大于等于(g   e)100 ,   cl=1   否则 cl = 0
setge=>set   +g (Greate) + e (Equal)

xorecx ,ecx
cmpeax,100
setgecl
dececx

上式意思是   eax跟100比较   如果大于等于(g   e)100 ,   ecx = 0   否则 ecx= -1
```

```
#include <stdio.h>
int main(int argc, char* argv[])
{
   printf("%d\r\n",argc >= 100 ? 32 : 48);
   return 0;
}

反汇编代码:
xor   eax, eax
cmp   , 64h      ; 跟100比较
setl      al                           ;如果小于 al = 1 否则 al = 0
dec      eax                     ; 变成0 和 -1
and      al, 0F0h            ; 0F0h 是 -16
add      eax, 30h ;          ;加上 48
push    eax
push   offset Format   ; "%d\r\n"
call    _printf
```

#### cmovxx 指令

在**高版本**中:

经过   cmp/test / sub / add 等处理(影响标志)之后, 后面出现了cmovxx   reg32 , reg32

A>= 100 ? 32 : 48

moveax , 32

movecx , 48

cmpA,100

cmovleax,ecx

上式意思是   ,eax = 32,ecx = 48 ,比较 A 和 100 的大小,如果小于 ,就把eax = ecx否则值不变

```
int main(int argc, char* argv[])
{
    printf("%d\r\n", argc == 0 ? 0 : -1);
    return 0;
}
反汇编代码
mov   eax,
neg   eax
sbb   eax, eax
push    eax
push    offset _Format; "%d\r\n"
call    _printf
如果是 0 和 -1 就不会使用 cmovxx 指令 ,跟低版本一样

int main(int argc, char* argv[])
{
    printf("%d\r\n", argc == 0 ? 100 : -100 );
    return 0;
}
反汇编代码
push    ebp
mov   ebp, esp
cmp   , 0
mov   ecx, 64h
mov   eax, 0FFFFFF9Ch
cmovz   eax, ecx
push    eax
push    offset _Format; "%d\r\n"
call    _printf
```

### 常见优化方案

编译器的工作过程可以

分为几个阶段:

1. 预处理      :除以 以 # 号开头的文件,对源码进行查找替换,得到的还是源码
2. 词法分析   : 运算符,表达式 ,生成表达式 树

例如:   计算一个表达式   " a + b * c - d / e "

最简单的方法是生成一个表达式树

!(./notesimg/1657531661827-0e425f17-3b31-422e-acf6-373833946695.png)

中序遍历   :    a + b * c - d / e

先序遍历   :    - + a * b c / d e         (波兰式 : 前缀表达式,操作在前,操作树在后)

后序遍历:   a b c * + d e / -

人类习惯的是 中序

c 和 汇编语言 习惯先序   (容易转成函数调用)

c 用函数调用来表达上式       sub(add (a mul (b,c) ),div(d,e) )

中国传统是 逆波兰表达式 ,操作数在前,操作符在后

1. 语法分析
2. 语义分析    :转成流程图
3. 中间代码生成   : 把图换成语言再存储
4. 目标代码生成。 : 跟平台相关,生成机器指令

其中,优化的机会一般存在于中间代码生成和目标代码生成这两个阶段

**常见的与设备无关的优化方案有以下几种:**

\1. 常量折叠

\2. 常量传播

\3. 减少变量

\4. 公共表达式

\5. 复写传播

\6. 剪去不可达分支(剪支优化)

\7. 顺序语句代替分支

\8. 强度削弱

\9. 数学变换

\10. 代码外提

```
    unsignedint u = 10;
    intn = 20;

    printf("请输入2个整数,以空格分隔\r\n");
    scanf_s("%d %d", &u, &n);

    printf("%d\r\n", u % 16);
    printf("%d\r\n", u % 9);
    printf("%d\r\n", u % 2);

    printf("%d\r\n", n % 2);
    printf("%d\r\n", n % 11);
    printf("%d\r\n", n % 8);
    printf("%d\r\n", n % -4);

    printf("%d\r\n", n == 10 ? 6 : 5 );
    printf("%d\r\n", n == 10 ? 5 : 6);

    printf("%d\r\n", n >= 10 ? 10 : 20);
    printf("%d\r\n", n >= 10 ? 20 : 10);



.text:004010A0               push    esi
.text:004010A1               push    edi             ; ArgList
.text:004010A2               push    offset Format   ; "请输入2个整数,以空格分隔\r\n"
.text:004010A7               mov   dword ptr , 0Ah
.text:004010AE               mov   , 14h
.text:004010B5               call    sub_401020
.text:004010BA               lea   eax,
.text:004010BD               push    eax
.text:004010BE               lea   eax,
.text:004010C1               push    eax             ; Arglist
.text:004010C2               push    offset aDD      ; "%d %d"
.text:004010C7               call    sub_401050
.text:004010CC               mov   eax, dword ptr
.text:004010CF               and   eax, 0Fh
.text:004010D2               push    eax             ; ArgList
.text:004010D3               push    offset aD       ; "%d\r\n"
.text:004010D8               call    sub_401020
.text:004010DD               mov   ecx, dword ptr
.text:004010E0               mov   eax, 38E38E39h
.text:004010E5               mul   ecx
.text:004010E7               shr   edx, 1
.text:004010E9               lea   eax,
.text:004010EC               sub   ecx, eax
.text:004010EE               push    ecx             ; ArgList
.text:004010EF               push    offset aD       ; "%d\r\n"
.text:004010F4               call    sub_401020
.text:004010F9               mov   eax, dword ptr
.text:004010FC               and   eax, 1
.text:004010FF               push    eax             ; ArgList
.text:00401100               push    offset aD       ; "%d\r\n"
.text:00401105               call    sub_401020
.text:0040110A               mov   eax,
.text:0040110D               and   eax, 80000001h
.text:00401112               jns   short loc_401119
.text:00401114               dec   eax
.text:00401115               or      eax, 0FFFFFFFEh
.text:00401118               inc   eax
.text:00401119
.text:00401119 loc_401119:                           ; CODE XREF: _main+82↑j
.text:00401119               push    eax             ; ArgList
.text:0040111A               push    offset aD       ; "%d\r\n"
.text:0040111F               call    sub_401020
.text:00401124               mov   ecx,
.text:00401127               mov   eax, 2E8BA2E9h
.text:0040112C               imul    ecx
.text:0040112E               sar   edx, 1
.text:00401130               mov   eax, edx
.text:00401132               shr   eax, 1Fh
.text:00401135               add   eax, edx
.text:00401137               imul    eax, 0Bh
.text:0040113A               sub   ecx, eax
.text:0040113C               push    ecx             ; ArgList
.text:0040113D               push    offset aD       ; "%d\r\n"
.text:00401142               call    sub_401020
.text:00401147               mov   eax,
.text:0040114A               and   eax, 80000007h
.text:0040114F               jns   short loc_401156
.text:00401151               dec   eax
.text:00401152               or      eax, 0FFFFFFF8h
.text:00401155               inc   eax
.text:00401156
.text:00401156 loc_401156:                           ; CODE XREF: _main+BF↑j
.text:00401156               push    eax             ; ArgList
.text:00401157               push    offset aD       ; "%d\r\n"
.text:0040115C               call    sub_401020
.text:00401161               mov   eax,
.text:00401164               add   esp, 40h
.text:00401167               cdq
.text:00401168               xor   eax, edx
.text:0040116A               sub   eax, edx
.text:0040116C               and   eax, 3
.text:0040116F               xor   eax, edx
.text:00401171               sub   eax, edx
.text:00401173               push    eax             ; ArgList
.text:00401174               push    offset aD       ; "%d\r\n"
.text:00401179               call    sub_401020
.text:0040117E               xor   eax, eax
.text:00401180               cmp   , 0Ah
.text:00401184               setz    al
.text:00401187               add   eax, 5
.text:0040118A               push    eax             ; ArgList
.text:0040118B               push    offset aD       ; "%d\r\n"
.text:00401190               call    sub_401020
.text:00401195               xor   eax, eax
.text:00401197               cmp   , 0Ah
.text:0040119B               setnz   al
.text:0040119E               add   eax, 5
.text:004011A1               push    eax             ; ArgList
.text:004011A2               push    offset aD       ; "%d\r\n"
.text:004011A7               call    sub_401020
.text:004011AC               mov   esi, 0Ah
.text:004011B1               mov   edi, 14h
.text:004011B6               cmp   , esi
.text:004011B9               mov   eax, edi
.text:004011BB               cmovgeeax, esi
.text:004011BE               push    eax             ; ArgList
.text:004011BF               push    offset aD       ; "%d\r\n"
.text:004011C4               call    sub_401020
.text:004011C9               cmp   , esi
.text:004011CC               cmovgeesi, edi
.text:004011CF               push    esi             ; ArgList
.text:004011D0               push    offset aD       ; "%d\r\n"
.text:004011D5               call    sub_401020
.text:004011DA               push    offset Command; "pause"
.text:004011DF               call    ds:system
```

veneryz 发表于 前天 02:38

{:-_-qiang-_-:}分析计算优化 很厉害啊

YINXN 发表于 前天 09:24

膜拜神贴,后面的请保持队形~
页: [1]
查看完整版本: X86C++反汇编05.除法优化-取模 和三目运算