都是无限循环,谁快?

for版无限循环和while版,有何区别?

今天偶然想到一个问题,日常开发中涉及无限循环的时候,通常有两种方式:
for ( ; ; )while (1),那么问题来了,这两种真的完全相同?

探究主题

for无限和while无限谁快

控制变量

分别使用cl编译器和gcc编译器,编译同一段既含有for,也含有while的c源码,反汇编其目标文件,根据指令具体分析

源码

int func_for_test(void)
{
	printf("!!!\n");
	return 0;
}

void test_for_loop(void)
{
	for (;;)
	{
		func_for_test();
	}
}

void test_while_loop(void)
{
	while (1)
	{
		func_for_test();
	}
}

输出结果

(均不开启优化选项,输出原生)

gcc

   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   48 8d 05 00 00 00 00    lea    rax,[rip+0x0]
   f:   48 89 c1                mov    rcx,rax
  12:   e8 00 00 00 00          call   17 <func_for_test+0x17>
  17:   b8 00 00 00 00          mov    eax,0x0
  1c:   48 83 c4 20             add    rsp,0x20
  20:   5d                      pop    rbp
  21:   c3                      ret

0000000000000022 <test_for_loop>:
  22:   55                      push   rbp
  23:   48 89 e5                mov    rbp,rsp
  26:   48 83 ec 20             sub    rsp,0x20
  2a:   e8 d1 ff ff ff          call   0 <func_for_test>
  2f:   eb f9                   jmp    2a <test_for_loop+0x8>

0000000000000031 <test_while_loop>:
  31:   55                      push   rbp
  32:   48 89 e5                mov    rbp,rsp
  35:   48 83 ec 20             sub    rsp,0x20
  39:   e8 c2 ff ff ff          call   0 <func_for_test>
  3e:   eb f9                   jmp    39 <test_while_loop+0x8>

CL

0000000000000000 <func_for_test>:
   0:   48 83 ec 28             sub    rsp,0x28
   4:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]
   b:   e8 00 00 00 00          call   10 <func_for_test+0x10>
  10:   33 c0                   xor    eax,eax
  12:   48 83 c4 28             add    rsp,0x28
  16:   c3                      ret
  17:   cc                      int3
// int3...
  1f:   cc                      int3

0000000000000020 <test_for_loop>:
  20:   48 83 ec 28             sub    rsp,0x28
  24:   e8 00 00 00 00          call   29 <test_for_loop+0x9>
  29:   eb f9                   jmp    24 <test_for_loop+0x4>
  2b:   48 83 c4 28             add    rsp,0x28
  2f:   c3                      ret
  30:   cc                      int3
// int3...
  3f:   cc                      int3

0000000000000040 <test_while_loop>:
  40:   48 83 ec 28             sub    rsp,0x28
  44:   33 c0                   xor    eax,eax
  46:   83 f8 01                cmp    eax,0x1
  49:   74 07                   je     52 <test_while_loop+0x12>
  4b:   e8 00 00 00 00          call   50 <test_while_loop+0x10>
  50:   eb f2                   jmp    44 <test_while_loop+0x4>
  52:   48 83 c4 28             add    rsp,0x28
  56:   c3                      ret

分析

gcc

首先看到func_for_test()的代码。实际上就是利用RIP相对寻址找到常量池里面“!!!\n”的地址,然后传参给printf,调用,返回。
需要解释的是两个测试函数中,jmp指令的地址。这里从obj文件即可看出,位于0x2f的JMP指令,转跳目的地在0x2a,看一下就能知道0x2a的指令是CALL func_for_test
也就实现了循环调用,这是for-loop的测试。观察可得,while生成的机器码一模一样几乎,仅仅转跳地址更新了下,相对于本函数偏移罢了。

CL

这里的区别可就大了,for-loop的测试明显短一些。可能疑惑的点在于,位于0x14的CALL指令,的目标函数竟然是0x19,objdump给我们的提示是相对于test_for_loop偏移0x09,但是这 可能有特殊意义,不然程序的逻辑不就乱了吗?明明调用了test函数却不显示,这肯定有环节出了问题。基于此,我们来测试下这个程序能不能运行。
测试方法特别简单,使用gcc把它编译成一个exe然后执行就行了。
设输出文件名=a.obj
那么执行gcc a.obj -o a.exe -etest_for_loop -nostartfiles
这里的参数解释下,-e选项指定入口点,-nostartfiles指定不允许链接启动库,也就不会默认调用__main函数。这时程序会从test_for_loop()函数开始执行。
链接成exe后运行,发现确实不断打印!!!(虽然。。系统很难受),摁ctrl+C终止。
这说明程序没问题,那么问题在哪?反汇编exe看看。
调用地址正常!
可以看见调用地址是指向test函数的,那么初步分析应该是objdump显示的是符号表中相对于test_for_loop的偏移而非实际代码段中的偏移。

我们再看到while的代码
很容易翻译:
XOR EAX, EAX 清零EAX寄存器
CMP EAX, 0x01 比较EAX(0)和1,肯定不相等啊!
JE 0x52 如果相等就跳出循环,否则继续执行下面的代码
调用test函数
JMP 0x44 跳到XOR那一行,再次检测循环条件是否成立(肯定成立啊,1!=0)
然后就是平栈和返回(永远无法到达,never reach!)

结论

根据CL的情况来看,如果gcc循环体内代码更加复杂,那么可能也是一样的情况:for比while短,且每次循环都要执行的语句更少,时空复杂度都更低.
究其根本,可以理解成for (;;)里面一个表达式都没有,计算自然简便
while (1)有个1,不比较怎么行?所以指令更长

综上所述:一般来说,for版本的无限循环比while版本的无限循环更有效率,更优。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dtsroy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值