mov ecx, 16
looptop: .
.
.
loop looptop
此循环执行多少次?
如果ecx = 0
开始,会发生什么?loop
在这种情况下会跳还是掉下去?
loop
完全一样dec ecx / jnz
,只是它没有设置标志。
就像do{} while(--ecx != 0);
C中a的底部。如果执行使用进入循环ecx = 0
,则环绕表示循环将运行2 ^ 32次。(或者在64位模式下为2 ^ 64倍,因为它使用RCX)。
与不同rep movsb/stosb/etc.
,它不会在递减之前(仅在递减之后)检查ECX = 0。
地址大小确定它是否使用CX,ECX或RCX。因此,在64位代码中,addr32 loop
就像dec ecx / jnz
,而常规代码loop
一样dec rcx / jnz
。或在16位代码中,它通常使用CX,但地址大小前缀(0x67
)将使其使用ecx
。正如英特尔手册所说,它忽略了REX.W,因为它设置了操作数大小,而不是地址大小。
相关:为什么循环总是被编译成“ do ... while”风格(尾跳)?更多有关循环结构,ASM,while(){}
与do{}while()
和如何将它们布置。
如果你想了解指令的详细信息,请查看该手册:英特尔的官方vol.2 PDF指令集参考手册,或html摘录,其中每个条目都位于不同的页面上(http://felixcloutier.com/x86/)。但是请注意,HTML省略了介绍和附录,其中包含有关如何解释内容的详细信息,例如对于诸如的说明说“根据结果设置了标志”时add
。
你也可以(而且应该)在调试器中尝试一些操作:单步执行,并观察寄存器的变化。使用一个较小的起始值,ecx
这样你就可以ecx=1
更快地到达有趣的部分。另请参见x86标签Wiki,以获取底部的手册,指南和asm调试提示的链接。
顺便说一句,如果未显示的循环内指令进行了修改ecx
,则它可以循环任意次。为了使问题有一个简单而独特的答案,你需要保证标签和说明之间的loop
说明不被修改ecx
。(他们可以保存/恢复,但如果你打算这样做,通常最好是只使用一个不同的寄存器作为循环计数器。 push
/pop
内循环,使你的代码难以阅读。)
LOOP
即使你已经需要在循环中增加其他内容,也要过度使用。 LOOP
不是循环的唯一方法,通常是最糟糕的方法。
通常不要使用循环指令,除非以速度为代价来优化代码大小,因为它很慢。编译器不使用它。(因此,CPU供应商不必费心将其加快;赶上22。)使用dec / jnz
或完全不同的循环条件。(另请参见http://agner.org/optimize/,以了解更多有关效率的信息。)
循环甚至不必使用计数器。比较指针和结束地址或检查其他条件通常更好,甚至更好。(无意义的使用loop
是我的烦恼之一,特别是当你已经在另一个寄存器中有某些东西可以用作循环计数器时。)cx
用作循环计数器时,当你可以使用cmp
/时,通常只会占用你宝贵的几个寄存器之一jcc
在另一个寄存器上,你无论如何都要递增。
IMO,loop
应该被认为是那些不适合初学者的晦涩的x86指令之一。像stosd
(不带rep
前缀)aam
或xlatb
。不过,在优化代码大小时,它确实具有实际用途。(这在现实生活中有时对机器代码(例如引导扇区)有用,而不仅仅是像代码高尔夫之类的东西。)
IMO,只是教/学条件分支的工作原理,以及如何使它们循环。这样你就不会陷入思考使用循环的特殊情况loop
。我已经看到一个SO问题或评论,上面写着“我认为你必须声明循环”之类的内容,却没有意识到那loop
只是一条指令。
</rant>
。就像我说的,loop
是我的宠儿之一。除非你针对实际的8086进行了优化,否则这是一个晦涩的代码查询指令。
我决定将其发布,以便我们对将来的任何“
loop
工作原理”问题有一个规范的答案。这个答案有太多细节
@ineedahero:然后在第一句话或第一段之后停止阅读。那正好描述了它的正常运行。
哈!如果仅仅是“太多细节”是一个普遍的问题。感谢@PeterCordes,我发现这个答案非常有用且很有教育意义。我要指出的是,在发现.NET运行时发出的指令中出现循环之后,我进入了这里,所以我认为编译器不使用它的情况并非如此。另外,如果它是由.NET运行时发出的,则在给定该组花费大量时间进行分析和优化的情况下,它必须相当快。
@ N8allan:在这种情况下,.NET是否针对AMD CPU进行了调优?我很好奇什么环境,因为
loop
并且loope
在Intel CPU上仍然很慢。(请参阅循环指令为什么慢?英特尔不能有效地实现它吗?)。您是否有可能从本来不是指令的开始部分开始反汇编,因此您的反汇编在一段时间内不同步?还是您确定它实际上正在运行。