确定寄存器的值是否等于零的最简单方法是什么?

What's the easiest way to determine if a register's value is equal to zero or not?

我在 Irvine 库中使用 x86 程序集。

检查寄存器值是否为零的最简单方法是什么?

我使用了 cmp 指令,但我正在寻找替代方法。 这是我使用 cmp 指令的代码,寄存器是 ebx

    cmp ebx,0
    je equ1
    mov ebx,0
    jmp cont
equ1:
    mov ebx,1
    jmp cont
cont:
    exit

这个 "booleanizes" 一个值,产生 0 或 1,就像 int ebx = !!ebx 在 C 中一样。

您可以使用:

or ebx, 0 ;This does nothing, just triggers the zero flag if ebx is zero
jnz notZero 
or ebx, 1 ;ebx was zero, then ebx is 1
notZero:

可能 "easiest" 或最简单的 "not-caring about details" 回答如何 确定 是:

    ; here ebx is some value, flags are set to anything
    test   ebx,ebx   ; CF=0, ZF=0/1 according to ebx
    jz     whereToJumpWhenZero
    ; "non-zero ebx" will go here

    ; Or you can use the inverted "jnz" jump to take
    ; a branch when value was not zero instead of "jz".

有一个关于设置标志等的详细 [​​=32=] 推理。还有一个 link 另一个类似的答案,但从性能的角度推理为什么这是最好的方法。 :)

如何在ebx为零时将其他一些寄存器(我会选择eax)设置为1,在ebx非零时设置为0(非破坏性方式ebx 本身):

    xor   eax,eax  ; eax = 0 (upper 24 bits needed to complete "al" later)
    test  ebx,ebx  ; test ebx, if it is zero (ZF=0/1)
    setz  al       ; al = 1/0 when ZF=1/0 (eax = 1/0 too)

或者当ebx为zero/non-zero时如何将ebx本身转换为1/0:

    neg   ebx      ; ZF=1/0 for zero/non-zero, CF=not(ZF)
    sbb   ebx,ebx  ; ebx = 0/-1 for CF=0/1
    inc   ebx      ; 1 when ebx was 0 at start, 0 otherwise

或者当 ebx 为 zero/non-zero 时如何将 ebx 本身转换为 1/0,其他变体(在 "P6" 到 "Haswell" 核心上更快) :

    test  ebx,ebx  ; ZF=1/0 for zero/non-zero ebx
    setz  bl       ; bl = 1/0 by ZF (SETcc can target only 8b r/m)
    movzx ebx,bl   ; ebx = bl extended to 32 bits by zeroes

等等,等等...这取决于测试之前发生的事情,以及您真正想要的测试输出,有 许多 可能的方式(针对不同的最佳方式情况,并针对不同的目标进行优化 CPU).


我再补充几个极其常见的情况...一个计数器循环从N向下计数到零,循环N次:

    mov   ebx,5   ; loop 5 times
exampleLoop:
    ; ... doing something, preserving ebx
    dec   ebx
    jnz   exampleLoop ; loop 5 times till ebx is zero

如何处理 word (16b) 数组的 5 个元素(按数组[0]、数组[1]、...顺序访问它们):

    mov   ebx,-5
    lea   esi,[array+5*2]
exampleLoop:
    mov   ax,[esi+ebx*2]  ; load value from array[i]
    ; process it ... and preserve esi and ebx
    inc   ebx
    jnz   exampleLoop  ; loop 5 times till ebx is zero

再举一个例子,我很喜欢这个:

ebx 为 zero/non-zero 并且您已经有值 1 时,如何将目标寄存器(示例中的 eax)设置为 ~0 (-1)/0在某些寄存器中(例如 ecx):

    ; ecx = 1, ebx = some value
    cmp   ebx,ecx  ; cmp ebx,1 => CF=1/0 for ebx zero/non-zero
    sbb   eax,eax  ; eax = -1 (~0) / 0 for CF=1/0 ; ebx/ecx intact

-1 可能看起来与 1 一样实用(至少用于索引目的),但 -1 也可用作进一步 and/xor/or 操作的完整位掩码,因此有时它更方便。

使用旗帜,卢克
您可以通过检查零标志来测试寄存器是否为零。
如果寄存器通过一些影响标志(或者更具体地说,零标志)的操作获得了它的值,那么你不需要做任何事情,因为零标志已经反映了存储在该寄存器中的值。

仅在需要时进行测试
如果您不能保证已设置标志,则必须使用测试操作。
这些操作有两种形式:破坏性和非破坏性。

您可以在以下位置查看指令列表及其更改的标志:http://ref.x86asm.net—more specifically, at: http://ref.x86asm.net/coder32-abc.html

movlea 指令从不改变标志,因此需要帮助。大多数其他指令至少设置一个标志。

不要创建错误的依赖关系
如果您需要测试一个寄存器是否为零,但不想改变它的值,您可以使用 test 指令。
您不应该使用 orand 指令来检查寄存器,因为 CPU 可能不知道 or/and 可以非破坏性地使用并且可能无法应用某些优化。 这个的技术术语是 'false dependency'。寄存器需要 ebx 并且 'thinks' 它最近被更改了,所以它等待结果最终确定。

test ebx, ebx  ; <-- CPU knows ebx was not altered, no stalls on subsequent reads.
or   ebx, ebx  ; <-- CPU 'thinks' ebx was changed, stall on subsequent read.

如果你想让零状态反映在另一个寄存器中,你可以简单地mov ebx 到另一个寄存器。

将值简化为布尔值
如果您想将寄存器缩减为布尔值(如果非零则为 True,否则为 False),您可以使用以下序列之一:

; ebx holds the value to reduce to a boolean.
; eax is an unused register.
xor eax, eax   ; eax = 0
sub eax, ebx   ; eax = 0 - ebx; CF (carry flag) = 1 if ebx <> 0
sbb ebx, ebx   ; ebx = ebx - ebx - CF
               ; <<-- ebx = -1 if non zero, 0 if zero
xor eax, eax   ; eax = 0
sub eax, ebx   ; eax = - ebx; CF = 1 if ebx <> 0
adc ebx, eax   ; ebx = (ebx + -ebx) aka 0 + CF
               ; <<== ebx = 1 if non zero, 0 if zero
test ebx, ebx  ; force ZF to be correct
setnz al       ; Store 1 if non-zero, 0 otherwise in byte register AL.

请注意,由于与 "partial register writes" 相关的停顿,字节寄存器的使用可能会出现问题。