Assembly 基于x86程序集中标志的值,条件跳转是如何工作的?

Assembly 基于x86程序集中标志的值,条件跳转是如何工作的?,assembly,x86,conditional-statements,branch,Assembly,X86,Conditional Statements,Branch,我正在一步一步地阅读JeffDuntemann的汇编语言,我对一些条件跳转是如何工作的感到困惑。我知道CMP用于使用减法比较两个值,然后丢弃结果以设置标志 有没有办法确定哪些标志需要设置/取消设置?我了解JE和JNE的情况,它查看ZF是否已设置,但我不确定其他分支操作 以下是我一直坚持的部分: ClearLine: pushad ; Save all caller’s GP registers mov edx,15 ;

我正在一步一步地阅读JeffDuntemann的汇编语言,我对一些条件跳转是如何工作的感到困惑。我知道
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
我们不是说“如果第一个操作数(
edx
)大于或等于第二个操作数(
1
),则跳转”吗?但当我在调试器中测试它时,它显示它循环了16次,而不是15次。我不明白为什么会这样。

根据问题中的措辞,您的困惑似乎至少有一部分源于没有正确地将比较指令与条件跳转指令分开。
CMP
首先设置标志,然后根据标志的状态进行条件跳转分支。有许多不同的指令设置标志(几乎所有的算术和按位指令都设置标志;有关详细信息,请参阅每个指令的文档),但这些指令都没有任何分支。为了基于标志进行分支,您需要一条
Jcc
指令(其中
cc
是条件代码,指示它将检查的标志,例如
ae
,这意味着“高于或等于”)

我之所以指出这一点,是因为你说的是:

所以,通过基本上说CMP-edx,1,我们不是说“如果第一个操作数(edx)大于或等于第二个操作数(1)”

这可能只是描述实际发生的事情的一个捷径,但它仍然是一个不正确的心理模型,并将不可避免地导致混淆。
CMP
指令从不进行任何跳转。它所做的只是设置标志。您是正确的,它设置标志的方式与减法(
SUB
)完全相同,但只有执行读取标志并相应分支的
Jcc
指令,标志才会起任何作用

虽然您已经理解了它们,但我们将从
JE
/
JZ
JNE
/
JNZ
开始,因为它们是最容易理解的条件。这些只需查看零标志(
ZF
),并根据其状态进行分支
JE
完全等同于
JZ
。程序员可以选择两种不同的助记符,这两种助记符基于他们认为可以使代码更清晰、更易于阅读的内容。例如,当您执行
CMP
时,跟随
JE
通常是有意义的,因为从逻辑上讲,如果这两个值相等,您将跳转。从技术上讲,如果减法的结果是0,那么实际上是在跳转,因为
CMP
设置了类似
SUB
的标志,这就是为什么它100%等同于编写
JZ
,程序员不会经常这样做。相反,当您执行类似于
TEST reg,reg
的操作时,您经常会看到后面跟着
JZ
,因为如果上一次操作的结果为零,则将其视为跳跃更具语义。在条件中加入“not”有明显的效果

您可以找到一个非常有用的条件分支指令表。我仍然发现自己在定期查阅这张桌子或类似的东西。作为一个初学者,最有用的东西将是助记符的文本描述。作为一个更高级的程序员,最有用的是将助记符映射到所检查的实际标志。(实际的代码字节有时也很方便。)

如您所见,
JAE
表示“高于或等于时跳转”,这由进位标志的状态决定(
CF
)。如果未设置进位,则将执行分支。如果设置了进位,则执行将失败。正如表中所示,这对于无符号比较非常方便。为什么?因为这就是携带标志的用途。它比您需要的详细一点,但仍然包含相关的位,比如这些标志的定义

您还将在该图表中看到,
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并设置标志。然后,当且仅当未设置进位标志(
CF
)时,以下
JAE
指令查看标志的状态并分支回
.poke
(继续循环)

另一种思考方法是,当且仅当
EDX
中的值大于或等于1时,循环才会继续。从符号上来说,这只是:
EDX>=1
。E
do
{
    ...
}
while (edx-- >= 1);