看the c++ programming language时在“表达式和语句”这一章中有这样一个练习
void send(int *to, int *from, int count) {//Duff设施,有帮助的注释被有意删去了 int n = (count + 7) / 8; switch (count % 8) { case 0: do{ *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while (--n > 0); } }
问这个函数的作用?咋一看,多会觉得这个东西居然能运行。是的,他还是一种特殊的循环,我也对他能通过编译而感到好奇。
先从汇编代码看一下这个函数主要运行流程:
switch (count % 8)
013E5E90 mov eax,dword ptr [count] ;EAX = count
013E5E93 and eax,80000007h ; EAX = EAX % 8
013E5E98 jns send+3Fh (013E5E9Fh) ;处理可能出现的负数求模的情况
013E5E9A dec eax
013E5E9B or eax,0FFFFFFF8h
013E5E9E inc eax
013E5E9F mov dword ptr [ebp-0D0h],eax
013E5EA5 cmp dword ptr [ebp-0D0h],7 ;if (EAX > 7) 跳过switch
013E5EAC ja $LN10+0F3h (013E5FB2h)
013E5EB2 mov ecx,dword ptr [ebp-0D0h] ;ECX = EAX
013E5EB8 jmp dword ptr [ecx*4+13E5FBCh] ;跳入对应的switch处理例程
//switch这里有很多有意思的内容,但是先把汇编看完。
。。。。中间的那些 *to++ = *from++; 就不看了。。。。
接着:
while (--n > 0);
013E5F9F mov eax,dword ptr [n] ;EAX = N
013E5FA2 sub eax,1 ;EAX = EAX - 1
013E5FA5 mov dword ptr [n],eax ;N = EAX
013E5FA8 cmp dword ptr [n],0
013E5FAC jg $LN10 (013E5EBFh) ; if(N>0) jmp 013E5EBFh(case 1:的地址)
这里循环的013E5EBFh地址我们可以看到就是case 0 例程的地址
case 0:
do{ *to++ = *from++;
013E5EBF mov eax,dword ptr [to]
013E5EC2 mov ecx,dword ptr [from]
013E5EC5 mov edx,dword ptr [ecx]
也就是说如果循环条件成立程序会跳至case 0 的例程去运行。
总结一下这个函数运行方法:、
传入三个参数 to,from是2个数组,count是个int,然后 n = (count +7)%8, n代表了接下来的循环次数。
switch(count % 8) 跳入count与8的模的例程。
由于所有的例程都是 *to++ = *from++;而且没有break;
这整个函数的意义可以用一句话表示: COPY(to,from,count)将from数组中count个数的元素拷贝进to数组。
是不是觉得这个答案好无趣,废尽力气写了个没什么用的东西,一般我们都这样写数组拷贝的函数:
void my_send(int *to, int *from, int count)
{
for (int i = 0; i != count; ++i)
{
*to++ = *from++;
}
}
与他相比,这个奇怪的函数它的代码更长,但是由于for循环中每一次都要去判断 i != count,所以相对的这个奇怪的代码运行效率可能会更高一点
(这点我没去实际验证,希望有人去写个计时器算算看,现在计算机那么快估计速度差距1个毫秒都不到吧。。。。)
最后,看一个说过的有趣的地方:
为什么是8? 为什么将count去8的模然后分成8份(表达能力欠佳,虽然估计不会有人看,但是大家应该能懂我的意思),按照这个程序的逻辑无论是10还是100都是可以实现的。
我们看一下汇编:
switch (count % 8)
013E5E90 mov eax,dword ptr [count] ;EAX = count
013E5E93 and eax,80000007h ; EAX = EAX % 8
013E5E98 jns send+3Fh (013E5E9Fh) ;处理可能出现的负数求模的情况
013E5E9A dec eax
013E5E9B or eax,0FFFFFFF8h
013E5E9E inc eax
将可能出现的负数情况排除,这句汇编很有意思
013E5E93 and eax,80000007h ; EAX = EAX % 8
对二进制比较熟悉的话可以发现 一个数对8的模就是将它转换为2进制,然后留下最后面3位
比如 122 二进制 1111010 留下最后3位 010 他对8的模就是 2
如果我们使用其他的数字比如7,那么汇编就没那么简单了(下面是我直接手写的,可能会有问题)
mov eax, dword ptr [num] ;eax = num
mox ebx, 7 ;ebx = 7
idiv eax,ebx ;edx = eax %ebx
至少需要3行,还没有考虑会出现的负数问题与push pop的原数据保存
。。。。所以这到底有什么用呢。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。