基本知识,7/2 和 6/2 在计算机中的商都为3.C语言的除法不等同于数学意义中的除法。
C语言的除法。采用向零取整的方法。
-______________0_______________+
只有在除数为常量的情况下。编译器才会出现优化。
1、除数为正数,2的幂的除法
#include "stdafx.h" int main(int argc, char* argv[]) { int iNum = argc; printf("%d\r\n", iNum / 2); printf("%d\r\n", iNum / 4); return 0; }
对应Release版本的反汇编如下:
push esi mov esi, dword ptr [esp+8] mov eax, esi cdq sub eax, edx sar eax, 1 push eax push test.00407030 ; ASCII "%d",CR,LF call test.00401040 mov eax, esi add esp, 8 cdq and edx, 3 add eax, edx sar eax, 2 push eax push test.00407030 ; ASCII "%d",CR,LF call test.00401040 add esp, 8 xor eax, eax pop esi retn
是不是感觉很奇怪,怎么没有了除法指令。查阅CPU手册。除法的指令周期数。大得吓人。具体值得根据cpu型号。翻阅 手册。
所以。编译器想尽办法。就是为了避免产生除法。提高效率。
mov esi, dword ptr [esp+8] mov eax, esi cdq sub eax, edx sar eax, 1
cdq 是把符号位扩展到 edx. 然后做加法。我们知道。对于被除数为正的情况,其值不变。除以2的幂。等于做 sar右移。
对于被除数为负的情况。那么由于sar右移。等同于向下取整。由于C语言是向零取整。所以。必须想办法下整转上整。就满足了C语言的向零取整。
于是公式来了。 a为被除数,n为2的幂. (a + 2^n - 1)/2^n
对于 iNum / 2 那就是等于 (iNum + 2 - 1)/2 。常量折叠。等于(iNum + 1)/2。除以2等于sar右移一位。
那么就完美解决了除以2且无分歧的优化。指令的总周期。相对于div指令要减少很多。
在按照公式推导 iNum /4 的情况。(iNum + 2^2 - 1) / 2^2 。常量折叠。等于(iNum + 3) / 4。除以4等于sar右移两位。
对应的汇编代码如下。
mov eax, esi cdq and edx, 3 add eax, edx sar eax, 2
推荐本书籍:《C++反汇编与逆向分析技术揭秘》。
国内第一家从事软件逆向工程师培训的机构。http://www.51asm.com 强烈推荐!