除法
除数为变量的时候是没有优化,只能使用除法指令,我说的变量是和argc这类在编译期间不能计算的变量,如果不是的话则会进行常量传播优化,也就当成常量去优化除法了
向下取整:取一个数的小于这个数的最大整数,如3.5 向下则是3
向上取整:取一个数的大于这个数的最小整数,如3.5 向上则是4
向0取整:坐标轴靠近0的整数,3.5是3,-3.5是-3
1两个无符号整数相除,结果还是无符号的
2两个有符号整数相除,那么结果是有符号的
3有符号树和无符号数混除,那么结果是无符号的
Release版
无符号
1除数为变量
int main(unsigned int argc)
{
unsigned int a = 10;
printf("%d", a / argc);
}
2 除数为常量
2.1除数为2的幂
int main(int argc)
{
//如果被除数是常量的话就会变成 常量/常量 则会是常量折叠优化,编译器默认是按执行效率优化的
printf("%d", (unsigned int)argc / 4);
}
2.2除数为非2的幂
2.2.1MagicNumber无进位
公式为C(除数)=2^(32+n) / Magic 32是看是什么程序,32位程序就是32,16位就是16
**这个公式计算C是向上取整**
int main(int argc)
{
printf("%d", (unsigned int)argc / 3);
}
2.2.2 MagicNumber有进位
公式为C(除数) = 2n / (M + 2^32)
int main(int argc)
{
//Windows平台还需要满足乘减移加移
printf("%d", (unsigned int)argc / 7)
有符号
1除数为变量
int main(int argc)
{
int a = 10;
printf("%d", a / argc);
}
2 除数为常量
2.1 除数为正
2.1.1 除数为2的幂
有符号数除法不能直接移位:c语言中有符号除法规则是向0取整,而移位sar是属于向下取整,所以就会有问题,要调整为向上取整才符合向0取整,例如-7/2,如果用移位的话,得到的值是-4,也就是向下取整,而正确答案应该是向上取整,所以要调整一下
一般计算机整数相除,选择向下取整,负数相除,选择向上取整 ,所以下整转上整
代码定式:
cdq
// eax >= 0, edx = 0;
// eax < 0, edx = -1;
and edx, 3 //2^n -1 //检测n的值是否相等
add eax, edx
sar eax, 2 //n //检测n的值是否相等
eax / 2^n
公式是
除以2的时候会再次优化成一条指令
把指令
and edx, 3 //2^n -1
add eax, edx //eax+edx=eax+3
替换成
sub eax,edx //这就相当于eax–1==eax+1=eax+2的1次方-1
int main(int argc)
{
printf("%d", argc / 4);
}
2.1.2 除数为非2的幂
2.1.2.1 MagicNumber为正
套用公式
C=2^(32+n) / Magic
int main(int argc)
{
//注意除以3的时候没有看到sar edx,1这条指令
printf("%d", argc / 5);
/*
汇编代码如下
.text:00401003 mov eax, 66666667h
.text:00401008 imul [ebp+argc]
.text:0040100B sar edx, 1 ; 因为edx是乘积结果的高位,所以移动位数都要加上32,因为是32程序就是32位n=1,
.text:0040100D mov eax, edx ;固定的三条指令是进行取值调整,达到向0取整的目的
.text:0040100F shr eax, 1Fh
.text:00401012 add eax, edx
.text:00401014 push eax
.text:00401015 push offset format ; "%d"
.text:0040101A call _printf
*/
}
2.1.2.2 MagicNumber为负
套用公式
C=2^(32+n) / Magic
int main(int argc)
{
printf("%d", argc / 7);
/*
反汇编优化代码
.text:00401003 mov eax, 92492493h ;magicNumber为负
.text:00401008 imul [ebp+argc]
.text:0040100B add edx, [ebp+argc] ;这里多一步加法是为了调整为真正的乘积的值
.text:0040100E sar edx, 2
.text:00401011 mov eax, edx ;固定的三条指令是进行取值调整,达到向0取整的目的
.text:00401013 shr eax, 1Fh
.text:00401016 add eax, edx
.text:00401018 push eax
.text:00401019 push offset format ; "%d"
.text:0040101E call _printf
*/
}
相对于magicNumber为正多一条add的原因
;相对于magicNumber正数情况 多一步加法操作
用16位举例
16位任意数的补码=ffff+1-任意的数,这就是为什么补码要取反加1
;A * 8086 运算结果 注意以下均为16进制
;用到的公式:
A + ~A = FFFF
A + ~A + 1 = 10000 (求补码)
;计算机计算的结果
利用公式m=2^(32+n)/C 因为此时C是整数,所以m实际值为+M,而在编译后m是负数,用imul计算的是m的补码*a,而不是真正的值ma,相差的值看是32位还是16位程序,
举例16位程序
(举例m=ffff是实际值,此时m为+m,a=2,此时你用imul计算的话,因为高位为1,所以用ffff的补码*a 转成十进制计算
果-a=-1*2=-2,而实际值=ffff*a转换成十进制=65535*2=131070,假设差值x,-a+X=ffff*a==>X=ffff*a+a=10000*a
ffffa=-a+10000a 用imul计算的乘积-a放在dx.ax中,又刚好差值X=10000a 的乘积相当于把a放在高位dx中,所以实值
只需要高位相加就是正确的结果,加一条指令add dx,a(a是乘法因子)就完成了
)
A * -(10000 - 8086) ;magicNumber>=8000,所以m为负数 m补=-(2^16-m无)
A * (8086 - 10000)
8086a - 10000a=edx.eax 此时真值多减了10000a
8086a=edx.eax + 10000a
;因为imul计算结果多减了一个10000a,所以要再加回去,这才是真正的值
;因为10000a 的乘积相当于把a放在高位edx,eax的高位
;所以结果多一步汇编
add edx, 乘法因子(10000a)
2.2 除数为负
2.2.1 除数为2的幂
int main(int argc)
{
//和除数为正,除数为2的幂优化一样,多了一条neg指令而已
printf("%d", argc / -4);
}
2.2.2 除数为非2的幂
2.2.2.1 MagicNumber为正
2^(32+n) / neg(Magic)
int main(int argc)
{
printf("%d", argc / -7);
/*
汇编代码
.text:00401003 mov eax, 6DB6DB6Dh;magic为正
.text:00401008 imul [ebp+argc]
.text:0040100B sub edx, [ebp+argc];这里多一步减法是为了调整为真正的乘积的值
.text:0040100E sar edx, 2
.text:00401011 mov eax, edx ;固定的三条指令是进行取值调整,达到向0取整的目的
.text:00401013 shr eax, 1Fh
.text:00401016 add eax, edx
.text:00401018 push eax
.text:00401019 push offset format ; "%d"
.text:0040101E call _printf
*/
}
; 证明
利用公式m=2^(32+n)/C 因为此时C是负数m=2^(32+n)/-C,把负数提出来,所以m实际值为-M,而在编译后m是正数,imul算出来的结果也是有偏差的,一样要减去差值才是实际的值
A * 8086
;用到的公式:
A + ~A = FFFF
A + ~A + 1 = 10000 (求补码)
;计算机计算的结果
A * (10000 - 8086)
A * 10000 - 8086a
-8086a + 10000a
edx.eax + 10000a
因为乘法的结果多加了10000a,所以得把10000a的部分减去
sub edx, 乘法因子
2.2.2.2 MagicNumber为负
2^(32+n) / neg(Magic)
int main(int argc)
{
printf("%d", argc / -5);
/*
text:00401003 mov eax, 99999999h;magicNumber为负
.text:00401008 imul [ebp+argc]
.text:0040100B sar edx, 1
.text:0040100D mov eax, edx ;固定的三条指令是进行取值调整,达到向0取整的目的
.text:0040100F shr eax, 1Fh
.text:00401012 add eax, edx
.text:00401014 push eax
.text:00401015 push offset format ; "%d"
.text:0040101A call _printf
*/
}
总结:先判断是有符号还是无符号数,根据mul imul乘法指令去确定,然后根据MagicNumber进位和无进位的公式套用看看是否符合公式,如果符合则是除法,因为汇编指令可以自己构造
总结汇编代码定式
有符号除法
除数为变量
idiv reg ;单操作数
idiv reg,reg/Mem ;双操作数
除法为常量
除数为正
除数为2的幂
mov eax,mem
cdq
and edx,m ;m = 2^n-1
add eax,edx ;负数相当于加m 正数相当于不变
sar eax,n ;除数相当于 2^n
除数非2的幂
- MagicNumber为正
mov eax,Magic
imul reg ;reg保存了被除数
sar edx,n ;有符号移位这里满足:C(被除数) = 2^(32+n) / Magic
mov reg,edx ;固定的三条指令是进行调整的作用,这样取值达到了向0取整
shr reg,1Fh ;拿到符号位
add edx,reg ;负数相当于减 1 正数相当于不变,这样取值才是正确的
- MagicNumber为负
mov eax,Magic
imul reg ;reg保存了被除数
add edx,reg ;这里多一步加法是为了调整为
sar edx,n ;这里满足:C(被除数) = 2^(32+n) / Magic
mov reg,edx ;固定的三条指令是进行调整的作用,这样取值达到了向0取整
shr reg,1Fh ;拿到符号位
add edx,reg ;负数相当于减 1 正数相当于不变
除数为负
除数为2的幂
mov eax,mem
cdq
and edx,m ;m = 2^n-1
add eax,edx ;负数相当于加m 正数相当于不变
sar eax,n ;除数相当于 2^n
neg eax ;这里代表除数是负数,因为计算器是补码进行计算的,如果是负数的话,显示的时候要转成原码显示
除数为非2的幂
MagicNumber为正
;有符号Magic不该调整的调整了 说明被除数是负数
;套用公式计算的时候实际Magic需要取反加1
mov eax,Magic
imul reg ;reg保存了被除数这里满足:被除数 = 2^(32+n) / neg(Magic)
sub edx,reg
sar edx,n
mov reg,edx ;固定的三条指令是进行调整的作用,这样取值达到了向0取整
shr reg,1Fh ;拿到符号位
add edx,reg ;负数相当于减 1 正数相当于不变
MagicNumber为负
;有符号Magic该调整没调整 说明被除数是负数
;计算的时候实际Magic需要取反加1
mov eax,Magic
imul reg ;reg保存了被除数、这里满足:被除数 = 2^(32+n) / neg(Magic)
sar edx,n
mov reg,edx
shr reg,1Fh ;拿到符号位
add edx,reg ;负数相当于减 1 正数相当于不变
无符号除法定式总结
除数为变量
无符号除法
div reg
除数为常量
除数为2的幂
and edx,m ;m = 2^n-1
shr eax,n ;除数相当于 2^n
除数为非2的幂
MagicNumber有进位
mov eax,Magic ;这里满足:被除数 = (2^ (m + A)) / (2^32 + Magic)
mul reg ;reg保存了被除数
sub reg,edx
shr reg,m
add reg,edx
shr reg,A ;这句没有的话n值为1
MagicNumber无进位
mov eax,Magic ;这里满足:被除数 = 2^ (32 + n) / Magic
mul reg ;reg保存了被除数
shr edx,n
除法结论
利用imul和mul判断有无符号
imu有符号
1判断magic为正负
- magic为正,乘数和位移运算中间无调整,除数为正 ,如果有调整这是负数
- magic为负,乘数和位移运算中间有加法调整(乘积高位加一个乘法因子),除数为正,如果没有调整则是负数
记住负数neg(magicNumber)后套用公式C=2^32+n/magicNumber
mul无符号
- 只要在windows平台下不是乘减移加移的定式就是无进位的,也可以看代码的套用公式试以下,无进位的指令短,直接就是am>>n位