我正在阅读Jeff Duntemann的汇编语言循序渐进,我对一些条件跳转的工作原理感到困惑。我理解CMP
用于使用减法比较两个值,然后抛弃结果以设置标志。
有没有办法弄清楚需要设置/取消设置哪些标志?我理解JE
和JNE
它会查看ZF
是否已设置,但我不确定其他分支操作。
这是我坚持的部分:
ClearLine:
pushad ; Save all caller’s GP registers
mov edx,15 ; We’re going to go 16 pokes, counting from 0
.poke: mov eax,1
sub edx,1
jae .poke ; Loop back if EDX >= 0
popad
ret `
如果EDX> = 0,为什么JAE
会回来?如果EDX> = 1,它不会循环回来吗?毕竟,SUB
操作就像CMP
操作一样,但需要额外的步骤来保存结果。所以基本上说CMP edx,1
我们不会说#34;如果第一个操作数(EDX
)大于或等于第二个操作数(1)"则跳转?但是当我在调试器中测试时,它会显示它循环16次,而不是15次。我不明白为什么会这样。
答案 0 :(得分:2)
根据问题中的措辞,似乎至少部分困惑源于没有正确地将比较指令与条件跳转指令分开。 CMP
首先设置标志,然后条件跳转根据标志的状态进行分支。有许多不同的指令设置标志(几乎所有的算术和按位指令设置标志;有关详细信息,请参阅每条指令的文档),并且 none 中的任何分支都可以。为了根据标志进行分支,您需要Jcc
指令(其中cc
是条件代码,指示它将检查的标志,例如ae
,这意味着&# 34;上述或相等到"。)
我指出这一点的原因是因为你说的是:
所以基本上说CMP edx,我们说'#34;如果第一个操作数(EDX)大于或等于第二个操作数(1)"则跳转?
可能只是描述实际发生的快捷方式,但仍然是一个不正确的心理模型,不可避免地会导致混乱。 CMP
指令永远不会跳跃。它只是设置标志。您确定它将标记设置为与减法(SUB
)完全相同,但标志不会执行任何操作,直到您执行Jcc
为止相应地读取和分支的指令。
虽然您已经了解它们,但我们会从JE
/ JZ
和JNE
/ JNZ
开始,因为它们是最简单的条件了解。这些只是查看零标志(ZF
),并根据其状态进行分支。 JE
恰好等同于JZ
。只有两种不同的助记符允许程序员根据他们认为会使代码更清晰,更易于阅读的内容进行选择。例如,当您执行CMP
时,跟随JE
通常是有意义的,因为逻辑,如果两个值相等,您就会跳跃。 技术上,如果减法的结果是0,你实际上是跳跃的,因为CMP
设置了像SUB
这样的标志,所以这就是为什么它是100相当于写JZ
的%,你只是看不到程序员经常这样做。相反,当您执行TEST reg, reg
之类的操作时,您经常会看到JZ
后面的内容,因为如果最后一次操作的结果将其视为跳跃,则会更加语义化是零。添加"不是"对病情有明显的影响。
您可以找到条件分支指令here的非常有用的表格。我仍然发现自己经常咨询这张桌子或类似的东西。作为初学者,最有用的是助记符的文本描述。作为一个更高级的程序员,最有用的东西将成为助记符映射到被检查的实际标志。 (实际的代码字节有时也非常方便。)
正如您所看到的,JAE
表示"如果超过或等于"则跳跃,这取决于进位标志(CF
)的状态。如果未设置进位,则将采用分支。如果设置了进位,则执行将失败。正如该表也告诉您的那样,这对于 unsigned 比较非常方便。为什么?因为那是进位标志的用途。 I just wrote a lengthy answer explaining the carry and overflow flags here。它比您需要的更详细,但仍包含相关位,如这些标志的定义。
您还会在该图表中看到JAE
有多个助记符,就像我们在JE
和JZ
中看到的那样。替代助记符为JNB
和JNC
。第一个JNB
非常明显 - 这只是JAE
的反面。如果某个值高于或等于另一个值,那么它也不低于该值。 JNC
只是对跳转所基于的标志的更直接的描述:进位标志。同样,它在技术上并不重要,但如果您仔细选择,它通常会使您的代码在语义上更正确和可读。
通过这种概念性理解,让我们更详细地看一下您的代码:
mov edx, 15
.poke:
mov eax, 1
sub edx, 1
jae .poke
(我不喜欢你的格式化,所以我稍微重写了一遍。: - p)显然,这会将EDX
设置为15,然后进入循环。在循环内部,它从EDX
中减去1并设置标志。然后,当且仅当进位标志({{1} })未设置。
另一种思考方式是,当且仅当JAE
中的值高于或等于1时,循环才会继续。符号上,这只是:.poke
。当然,除了这个符号表达并不能正确表示我们正在进行无符号比较。正如我在上面链接的其他答案中提到的,CPU不知道或不关心值是有符号还是无符号。这是程序员要解释的东西。您使用相同的CF
(或EDX
)指令来执行有符号和无符号减法(比较)。之后您会看到哪些标志会发生什么变化。进位标志(EDX >= 1
)用于无符号减法/比较;溢出标志(SUB
)用于签名比较/减法。
让我们来看看CMP
的几个示例值,以确保我们理解逻辑。
第一次循环,当CF
为15时,OF
指令从15减去1.结果当然是14.因此,零标志({{1} })设置为0(因为结果为非零)。进位标志(EDX
)设置为0,因为没有进位(无无符号溢出)。溢出标志(EDX
)设置为0,因为没有签名溢出。符号标志(SUB
)设置为0,因为结果是无符号的(其符号标志,这是最重要的位,未设置,表示该值为正)。根据{{1}}的状态,ZF
将分支回CF
并继续循环。从逻辑上讲,您将保持循环,因为OF
(15)中的值高于或等于1.
同样的事情持续了一段时间。我们让循环旋转,然后在SF
为1时中断它。现在,CF
指令从1中减去1.结果为0.因此,JAE
是1(结果为零),.poke
为0(未发生符号溢出),EDX
为0(无进位,无符号溢出),EDX
为0(结果为无符号) )。那么,分支会被采取吗?是的,SUB
是0.逻辑上,1高于或等于1(当然等于)。
下次,ZF
为0,因此将从0中减去1.结果为-1。 OF
为0(结果为非零),CF
为0(未发生签名溢出),SF
为1(进位发生 - 即无符号溢出),{{1是1(结果已签名)。这次分支不,因为CF
是1.逻辑上,这是有道理的,因为0 不高于或等于1(记住,这是无符号比较。)
这就是为什么它总共循环16次。它在EDX
为15时循环,并且继续循环向上通过 ZF
为0.这是因为您的条件测试位于 bottom 循环。也就是说,用C表示法:
OF