运算符(除法)

来源:互联网 发布:js日期转换为数字 编辑:程序博客网 时间:2024/05/18 09:37

本节所有数学公式并不存在有符号无符号之说,需要加以区分

当除数为变量,被除数为常量 C / V


int _tmain(int argc, _TCHAR* argv[]){    int n;    unsigned m;    scanf("%d", &n);    scanf("%d", &m);    printf("%d", 8 / n);    printf("%d", 8 / m);    return 0;}

无论是 Debug版 还是 Release版 ,反汇编结构都是相似的,都是直接使用 div 或者 idiv 这种直观的除法指令。

有符号变量

这里写图片描述

有符号变量作为除数生成的反汇编代码中,一般会成对编写 cdq 和idiv。

cdq:把 edx 的所有位都设成 eax 最高位的值。也就是说,当 eax < 0x80000000, edx = 0x00000000;当 eax >= 0x80000000, edx = 0xFFFFFFFF)。

idiv:表示有符号位的除法,相对应的无符号位除法 div。

无符号变量

这里写图片描述

无符号变量作为除数生成的反汇编代码中,一般会成对编写 xor 和div。


当被除数为变量,除数为常量(正数) V / C

int _tmain(int argc, _TCHAR* argv[]){    int n;    unsigned m;    scanf("%d", &n);    scanf("%d", &m);    printf("%d", n / 8);    printf("%d", m / 16);    printf("%d", m / 0x87654321);    return 0;}

有符号变量除以 2 的幂

Debug版Release版 生成的反汇编代码结构相似,生成反汇编如下:

这里写图片描述

该公式由数学公式推导而来,形成了下列定式:

cdqand edx, immAadd eax, edxsar eax, immB

2immB1=immA ,则该该块代码代表的是 有符号变量除以 2immB

无符号变量除以 2 的幂

Debug版Release版 生成的反汇编代码结构相似,生成反汇编结构如下:

这里写图片描述


无符号变量除以非 2 的幂(情况一)

Debug版 下,直接使用了指令 div

这里写图片描述

Release版 下,生成的汇编代码如下

这里写图片描述

该汇编结构也由数学公式推导而来,形成了下列定式:

mov eax, Mmul Ashr edx, imm

求除数的计算公式为:

C=2imm+32M


”粗糙“ 的公式推导求证

a 为无符号变量, c 为正数

ac=a×1×2nc×2n

=>
a×1×2nc×2n=a×2nc2n

=>设 M=2nc,那么 :

ac=a×M2n

结合代码,a×M2n 即对应上面的代码定式。


有符号变量除以非 2 的幂(情况一)

int _tmain(int argc, _TCHAR* argv[]){    printf("%d", argc / 5);    return 0;}

生成的反汇编如下:

这里写图片描述

其代码定式为:

mov eax, Mimul Asar edx, immmov reg, edxshr reg, 1Fhadd reg, edx

求除数的计算公式同样为:

C=2imm+32M

我们知道,这与 无符号变量除以非 2 的幂(情况一) 的汇编指令相似,不同的是,多了下列三条指令。这块指令仅是用于调整代码:如果最高位为 0,则无需调整(加 0 );如果最高位为 1,则需要加 1。在不同的平台上,可能会有不同的表现形式。

mov reg, edxshr reg, 31add reg, edx

无符号变量除以非 2 的幂(情况二)

int _tmain(int argc, _TCHAR* argv[]){    unsigned int n = 0;    scanf("%d", &n);    printf("%d", n / 7);    return 0;}

生成的反汇编代码如下:

这里写图片描述

代码定式为:

mov eax, Mmul Asub A, edxshr A, imm_1add A, edxshr A, imm_2

求除数的计算公式为:

C=2imm1+imm2+32M+232

“粗糙”的公式推导求证

从上面的 代码定式 中,可以知道:

edx=A×M232

如果将 代码定式 逐步执行,转成数学公式的话,得到以下式子:
AA×M2322imm1+A×M2322imm2

dadadadadadada….经过一系列转换,变成:
(232+M)×A232+imm1+imm2

无符号变量除以非 2 的幂(情况一) 中我们推导过公式:ac=a×M2n。如果将 (232+M)×A232+imm1+imm2 公式中的 (232+M)×A 视为一个新的 MagicNumber,那么也可以进一步得出:

ac=(232+M)×A232+imm1+imm2


有符号变量除以非 2 的幂(情况二)

int _tmain(int argc, _TCHAR* argv[]){    int n = 0;    scanf("%d", &n);    printf("%d", n / 7);    return 0;}

生成的反汇编代码如下:

这里写图片描述

代码定式为:

mov eax, Mimul Aadd edx, Asar edx, immmov reg, edxshr reg, 1Fhadd reg, edx

求除数的计算公式为:

C=2imm+32M

“粗糙”的推导求证—关于第三条指令 add edx, A

假设在 16 位系统环境下,有符号数 A 乘以 8086h,如果将 8086h 视为无符号相乘,那么代码如下:

mov ax, 8086himul Aadd dx, A

为什么最后需要有 add dx, A 这条指令呢?由于编译器是将 8086h 作为有符号相称,其结果用数学公式表示为:

R=A×8086hA×10000h

但实际上,A×8086h 才是我们需要的结果,所以需要将 A×10000h 这一部分加回来,也就是高位 edx + A。于是,我们也可以理解 定式代码 中的第三条指令是什么意思了。因为根据 无符号变量除以非 2 的幂(情况一) 公式推导, a×M 中的 M 实际上是一个无符号数。另外,剩下的三条指令也是用于调整代码的作用,与 有符号变量除以非 2 的幂(情况一) 中的代码调整相似。

为什么在这里 M 实际上是一个无符号数?因为 M 是由 2nc 计算出来的。在这里 c 一定是一个正数,所以 M 也一定是一个正数。


当被除数为变量,除数为常量(负数) V / C

如果无符号作为变量,常量为负数时,该常量也会被视为无符号,也就是 > 0x80000000。此时指令一定为 Div,在此不再讨论。下面只讨论有符号。

除以 2 的幂

int _tmain(int argc, _TCHAR* argv[]){    int n;    scanf("%d", &n);    printf("%d", n / -4);    return 0;}

生成的反汇编代码如下,没什么好说的了…:

这里写图片描述

除以非 2 的幂(情况一)

int _tmain(int argc, _TCHAR* argv[]){    int n;    scanf("%d", &n);    printf("%d", n / -3);    return 0;}

生成的反汇编代码如下:

这里写图片描述

代码定式:

mov eax, Mimul Asub edx, Asar edx, immmov reg, edxmov reg, 1Fhadd reg, edx

“粗糙”的解释

sub edx, A 这条指令为调整指令。当 M 为负数时,才需要有这条指令。如果该有调整指令而没有该指令,或者不该有调整指令而又调整指令,都需要对 M 求补来得到“真正的 M”。在这个情况下,其公式为:

C=2imm+32NOT(M)+1

除以非 2 的幂(情况二)

int _tmain(int argc, _TCHAR* argv[]){    int n;    scanf("%d", &n);    printf("%d", n / -9);    return 0;}

生成的反汇编代码如下:

这里写图片描述

代码定式:

mov eax, Mimul Asar edx, immmov reg, edxmov reg, 1Fhadd reg, edx

除以非 2 的幂(情况一) 中讨论过,由于是有符号乘法,M为负数,而没有调整代码,所以需要对 M 求补。

求除数的公式:

C=2imm+32NOT(M)+1

原创粉丝点击