上一篇说完加减乘的优化,这篇来说说除法,首先先打个铺垫,除法的优化涉及到各种数学公式,这里我们主要探讨一下结论,具体的证明可以参考《C++反汇编与逆向分析技术揭秘》,这里做一个总结。
首先,如果除数为变量时,是没有任何的优化空间的,所以老老实实的上对应的汇编代码即可,所以就不讨论了。下面主要来讨论下除数为常量的情况,并且这个常量还是正数,负数情况下一篇博客介绍。
首先,先来看一下需要讨论的情况
除数为常量-除数为正数情况
1.除数为2的幂-被除数无符号
2.除数为2的幂-被除数有符号
3.除数为非2的幂 - 被除数无符号
根据Magic Number的是否需要进位(下面简称M)
3.1 M值无进位
3.2 M值有进位
4.除数为非2的幂 - 被除数有符号
根据Magic Number的正负情况(下面简称M)
4.1 M值为正数
4.2 M值为负数
这个情况是有那么点多,不过其实总体算下来,应该就两套公式,分别为除数为2的幂和除数为非2的幂两套,只是情况不同,有些公式的变种而已。还有对于Magic Number这个概念,后面会提的,这里留个先个印象就好
首先,先来打一些基础的铺垫
1.乘法有无符号混合为有符号乘法(IMUL)
2.除法有无符号混合为无符号除法(DIV)
3.计算机中的除法为向零取整
4.移位运算是向下取整(不大于的关系)
首先对于向上取整和向下取整,对于计算机的同学应该十分熟悉了,其实对于向零取整其实就是往中间的零值靠齐
3 / 2 = 1.5 //往零靠齐取1 3/2=1
-3 / 2 = -1.5 //往零靠齐取-1 -3/2=-1
还有对于第四点一定要理解,因为在下面会有用到,移位运算,这里说的更细一点吧,对于除法需要用到的就是右移运算,你可以这样想,当二进制往右移动,那么肯定有一些位需要丢失,那么肯定是不大于的关系。
假设我们需要计算-5/2,那么我们很容易想到,使用右移一位,那么如下结果计算出来为-3(因为向下取整的原因),如果此时可以向上取整(向零取整),那么就符合我们的数学运算结果了。
-5>>1
FFFFFFFB >> 1
.......1011 >> 1
.......1101 -> FFFFFFFD = -3
首先,先来说下除数为2的幂的情况,这里我们可以使用右移,但是右移,对于负数的情况就会出现上面的问题,如下代码
for(int i=-20;i <= 20;++i)
{
printf("%d / 8 = %d\r\n",i,i/8);
printf("%d >> 3 = %d\r\n\r\n",i,i>>3);
}
打印运算结果,其实可以发现,在负数中的确会有问题,那么我们可以这样子来调整一下,加上n-1的值,n为除数
for(int i=-20;i <= 20;++i)
{
printf("%d / 8 = %d\r\n",i,i/8);
if(i >= 0)
printf("%d >> 3 = %d\r\n\r\n",i,i>>3);
else
printf("%d >> 3 = %d\r\n\r\n",i,(i+8-1)>>3);
}
部分结果
-13 / 8 = -1
-13 >> 3 = -1
-12 / 8 = -1
-12 >> 3 = -1
-11 / 8 = -1
-11 >> 3 = -1
-10 / 8 = -1
-10 >> 3 = -1
此时可以发现,结果吻合了,那么为什么加上n-1呢,你可以这样子想,负数右移与实际数学运算不符合的原因是因为右移向下取整了,那么如何才能向上取整呢,其实我们先来加上一个n值,你会发现大部分情况是对的
printf("%d >> 3 = %d\r\n\r\n",i,(i+8)>>3);
不正确的情况如下:
-16 / 8 = -2
-16 >> 3 = -1
-8 / 8