x86 LOOP 指令究竟是如何工作的?
How exactly does the x86 LOOP instruction work?
mov ecx, 16
looptop: .
.
.
loop looptop
这个循环会执行多少次?
如果 ecx = 0
开始会怎样? loop
在那种情况下是跳跃还是坠落?
loop
与 dec ecx / jnz
完全相同,只是它不设置标志 .
这就像C语言中do {} while(--ecx != 0);
的底部。如果执行进入循环ecx = 0
,wrap-around意味着循环将运行 2^32次。 (或者在64位模式下是2^64次,因为它使用了RCX。)
与rep movsb/stosb/etc.
不同,它在递减之前不检查ECX=0,仅在1.
之后
地址大小决定它是使用CX、ECX还是RCX。所以在 64 位代码中,addr32 loop
类似于 dec ecx / jnz
,而常规 loop
类似于 dec rcx / jnz
。或者在 16 位代码中,它通常使用 CX,但是地址大小前缀 (0x67
) 将使其使用 ecx
。正如英特尔手册所说,它忽略了 REX.W,因为它设置了操作数大小,而不是地址大小。
rep
字符串指令以相同的方式使用地址大小前缀,覆盖地址大小以及 RCX 与 ECX(或 64 位以外的模式下的 CX 与 ECX)。字符串指令的操作数大小已用于确定 movsw
与 movsd
与 movsq
,并且您希望 address/repeat 大小与其正交。让 loop
和 jrcxz
/jecxz
遵循该行为只是延续了 loop
的 8086 的设计意图,目的是在简单的 rep
时与字符串操作一起使用无法完成工作;见下文。
相关: 有关 asm 中循环结构的更多信息,while() {}
与 do {} while()
以及如何布置它们。
脚注 1:jcxz
(或 x86-64 jrcxz
)旨在用于 do {} while
样式循环的顶部之前, 如果应该 运行 0 次则跳过它。在现代 CPU 上 test rcx, rcx
/ jz
效率更高。
Stephen Morse,8086 的架构师,在他的书 The 8086 Primer 的那一节中写了关于 loop
/jcxz
的预期用途和字符串指令,可在他的网站上免费获得: https://www.stevemorse.org/8086/index.html. See the "complex string instructions" subsection, starting at the bottom of page 71. (Or start reading from earlier in the chapter, the whole String Instructions section starts on page 66. But note 这本书似乎解释得不好或不正确。)
如果您想了解 x86 指令的设计意图,您将找不到比这更好的来源。这与使用它们的最佳/最有效方式不同,尤其是在现代 x86 上,但对于初学者来说非常好的介绍,可以使用 asm 指令作为构建块来做什么。
额外的调试技巧
如果您想了解指令的详细信息,请查看手册:Intel's official vol.2 PDF instruction set reference manual, or an html extract with each entry on a different page (http://felixcloutier.com/x86/)。但请注意,HTML 省略了介绍和附录,其中包含有关如何解释内容的详细信息,例如当它说“根据结果设置标志”时,add
.[=71= 等指令]
而且您也可以(并且应该)在调试器中尝试一些东西:单步执行并观察寄存器的变化。为 ecx
使用较小的起始值,以便您更快地到达有趣的 ecx=1
部分。另请参阅 the x86 tag wiki 以获取底部的手册、指南和 asm 调试提示的链接。
顺便说一句,如果循环内未显示的指令修改 ecx
,它可以循环任意次数。为了让问题有一个简单而独特的答案,您需要保证标签和 loop
指令之间的指令不会修改 ecx
。 (他们可以 save/restore 它,但如果你打算这样做,通常最好只使用不同的寄存器作为循环计数器。 push
/pop
在循环中使你的代码难以阅读。)
抱怨 LOOP
的过度使用,即使您已经需要在循环中增加其他内容。 LOOP
不是唯一的循环方式,而且通常是最糟糕的方式。
您通常不应使用循环指令,除非以牺牲速度为代价来优化代码大小,because it's slow. Compilers don't use it. (So CPU vendors don't bother to make it fast; catch 22.) Use dec / jnz
, or an entirely different loop condition. (See also http://agner.org/optimize/ 以了解更多关于什么是有效的。)
循环甚至不必使用计数器;将指针与结束地址进行比较或检查其他条件通常也同样好,甚至更好。 (毫无意义地使用 loop
是我最讨厌的事情之一,尤其是当您在另一个寄存器中已经有一些东西可以用作循环计数器时。)使用 cx
作为循环计数器通常只会占用其中一个当您可以在另一个寄存器上使用 cmp
/jcc
时,您宝贵的几个寄存器无论如何都会递增。
IMO,loop
应该被认为是那些初学者不应该分心的晦涩的 x86 指令之一。像 stosd
(没有 rep
前缀)、aam
或 xlatb
。不过,它在优化代码大小时确实有实际用途。 (这有时在现实生活中对机器代码很有用(比如引导扇区),而不仅仅是像 code golf 这样的东西。)
IMO,只是教/学条件分支如何工作,以及如何从中创建循环。这样您就不会陷入认为使用 loop
的循环有什么特别之处的想法。我看到一个 SO 问题或评论说“我认为你必须声明循环”,但没有意识到 loop
只是一条指令。
</rant>
。就像我说的,loop
是我最讨厌的人之一。除非您针对实际的 8086 进行优化,否则这是一个晦涩的代码高尔夫指令。
mov ecx, 16
looptop: .
.
.
loop looptop
这个循环会执行多少次?
如果 ecx = 0
开始会怎样? loop
在那种情况下是跳跃还是坠落?
loop
与 dec ecx / jnz
完全相同,只是它不设置标志 .
这就像C语言中do {} while(--ecx != 0);
的底部。如果执行进入循环ecx = 0
,wrap-around意味着循环将运行 2^32次。 (或者在64位模式下是2^64次,因为它使用了RCX。)
与rep movsb/stosb/etc.
不同,它在递减之前不检查ECX=0,仅在1.
地址大小决定它是使用CX、ECX还是RCX。所以在 64 位代码中,addr32 loop
类似于 dec ecx / jnz
,而常规 loop
类似于 dec rcx / jnz
。或者在 16 位代码中,它通常使用 CX,但是地址大小前缀 (0x67
) 将使其使用 ecx
。正如英特尔手册所说,它忽略了 REX.W,因为它设置了操作数大小,而不是地址大小。
rep
字符串指令以相同的方式使用地址大小前缀,覆盖地址大小以及 RCX 与 ECX(或 64 位以外的模式下的 CX 与 ECX)。字符串指令的操作数大小已用于确定 movsw
与 movsd
与 movsq
,并且您希望 address/repeat 大小与其正交。让 loop
和 jrcxz
/jecxz
遵循该行为只是延续了 loop
的 8086 的设计意图,目的是在简单的 rep
时与字符串操作一起使用无法完成工作;见下文。
相关:while() {}
与 do {} while()
以及如何布置它们。
脚注 1:jcxz
(或 x86-64 jrcxz
)旨在用于 do {} while
样式循环的顶部之前, 如果应该 运行 0 次则跳过它。在现代 CPU 上 test rcx, rcx
/ jz
效率更高。
Stephen Morse,8086 的架构师,在他的书 The 8086 Primer 的那一节中写了关于 loop
/jcxz
的预期用途和字符串指令,可在他的网站上免费获得: https://www.stevemorse.org/8086/index.html. See the "complex string instructions" subsection, starting at the bottom of page 71. (Or start reading from earlier in the chapter, the whole String Instructions section starts on page 66. But note
如果您想了解 x86 指令的设计意图,您将找不到比这更好的来源。这与使用它们的最佳/最有效方式不同,尤其是在现代 x86 上,但对于初学者来说非常好的介绍,可以使用 asm 指令作为构建块来做什么。
额外的调试技巧
如果您想了解指令的详细信息,请查看手册:Intel's official vol.2 PDF instruction set reference manual, or an html extract with each entry on a different page (http://felixcloutier.com/x86/)。但请注意,HTML 省略了介绍和附录,其中包含有关如何解释内容的详细信息,例如当它说“根据结果设置标志”时,add
.[=71= 等指令]
而且您也可以(并且应该)在调试器中尝试一些东西:单步执行并观察寄存器的变化。为 ecx
使用较小的起始值,以便您更快地到达有趣的 ecx=1
部分。另请参阅 the x86 tag wiki 以获取底部的手册、指南和 asm 调试提示的链接。
顺便说一句,如果循环内未显示的指令修改 ecx
,它可以循环任意次数。为了让问题有一个简单而独特的答案,您需要保证标签和 loop
指令之间的指令不会修改 ecx
。 (他们可以 save/restore 它,但如果你打算这样做,通常最好只使用不同的寄存器作为循环计数器。 push
/pop
在循环中使你的代码难以阅读。)
抱怨 LOOP
的过度使用,即使您已经需要在循环中增加其他内容。 LOOP
不是唯一的循环方式,而且通常是最糟糕的方式。
您通常不应使用循环指令,除非以牺牲速度为代价来优化代码大小,because it's slow. Compilers don't use it. (So CPU vendors don't bother to make it fast; catch 22.) Use dec / jnz
, or an entirely different loop condition. (See also http://agner.org/optimize/ 以了解更多关于什么是有效的。)
循环甚至不必使用计数器;将指针与结束地址进行比较或检查其他条件通常也同样好,甚至更好。 (毫无意义地使用 loop
是我最讨厌的事情之一,尤其是当您在另一个寄存器中已经有一些东西可以用作循环计数器时。)使用 cx
作为循环计数器通常只会占用其中一个当您可以在另一个寄存器上使用 cmp
/jcc
时,您宝贵的几个寄存器无论如何都会递增。
IMO,loop
应该被认为是那些初学者不应该分心的晦涩的 x86 指令之一。像 stosd
(没有 rep
前缀)、aam
或 xlatb
。不过,它在优化代码大小时确实有实际用途。 (这有时在现实生活中对机器代码很有用(比如引导扇区),而不仅仅是像 code golf 这样的东西。)
IMO,只是教/学条件分支如何工作,以及如何从中创建循环。这样您就不会陷入认为使用 loop
的循环有什么特别之处的想法。我看到一个 SO 问题或评论说“我认为你必须声明循环”,但没有意识到 loop
只是一条指令。
</rant>
。就像我说的,loop
是我最讨厌的人之一。除非您针对实际的 8086 进行优化,否则这是一个晦涩的代码高尔夫指令。