如何检查emu8086中CF标志是否为1?

时间:2016-12-26 19:46:57

标签: assembly x86 emu8086 x86-16

我试图找出CARRY标志是否为1,但我不知道如何检查它。我写了下面的代码,但是我需要一些帮助来处理问号。

  LEA DX, MSG
  MOV AH, 09H
  INT 21H

  MOV AH, 01H
  INT 21H
  MOV NUM, AL

  SHR NUM, 1
  CMP ??, 1
  JE FINISH

FINISH: MOV AH, 4CH
  INT 21H

  NUM DB 0

  RET

3 个答案:

答案 0 :(得分:3)

您不能直接使用CMP指令,因为这些标志不是x86指令的有效操作数。它们仅由某些指令隐式使用。

最简单的解决方案就是使用a conditional branch。这类似于您已经熟悉的JE指令,除了它根据进位标志(CF)的值进行分支,而不是像JE那样的零标志(ZF)。< / p>

要有条件地分支进位标志(CF)的状态,您可以使用JCJNC。如果进位标志被设置(CF == 1),JC将分支,而如果进位标志设置(CF == 0),JNC将分支。这些操作码的助记符只是“ J ump如果 C arry”和“ J ump如果 N ot <强> C ARRY”。

jc  CarryFlagIsSet     ; jump if CF == 1
; else fall through: code for CF == 0 goes here

或者以另一种方式做到:

jnc CarryFlagIsNotSet  ; jump if CF == 0
; else fall through: code for CF == 1 goes here

因此,与您的示例一致,例如:

shr  num, 1        ; put least significant bit in CF
jc   num_was_odd   ; (or jnc LSBNotSet  aka num_was_even)

as Peter Cordes points out in a comment,您拥有的代码几乎肯定是错的,因为无论是否采用分支,都将执行相同的代码。换句话说,分支目的地等同于直通代码。你可能想要更像:

TOPOFLOOP:
  LEA DX, MSG
  MOV AH, 09H
  INT 21H

  MOV AH, 01H
  INT 21H
  MOV NUM, AL

  SHR NUM, 1
  JC  TOPOFLOOP     ; keep looping as long as CF == 1
                    ; otherwise, if CF == 0, fall through to FINISH
                    ; (use JNC to reverse the logic)

FINISH:
  MOV AH, 4CH
  INT 21H  
  RET

(除了操作注册值比存储在内存中的值 更快,所以如果可能,你应该将NUM放在寄存器中。此外,汇编语言操作码和寄存器是不区分大小写的,因此您可以轻松地以小写形式编写代码。我认为这更容易键入和更容易阅读,但它纯粹是风格,因此取决于您。)

如果您想编写无分支代码(几乎总能提高性能,如果您能够找到一种足够聪明的方法来编写代码),则可以使用SBB instruction。如果两个操作数都是相同的寄存器,如果进位标志置位(CF == 1),则将该寄存器设置为-1,或者如果未设置进位标志(CF == 0),则将该寄存器设置为0。这是有效的,因为SBB实际执行了操作DEST = (DEST - (SRC + CF))。如果DESTSRC的值相同,则相当于DEST = -CF

如果准确地使寄存器的值镜像CF更方便,可以将其与NEG instruction结合使用:

; Perform operation that will set CF.
...

; Set AX to the same value as CF.
sbb  ax, ax    ; CF ==  1 then AX = -1; otherwise, AX = 0
neg  ax        ; AX == -1 then AX =  1; otherwise, AX = 0

在某些情况下,您甚至可以以类似的方式使用ADC instruction。这将执行操作DEST = DEST + SRC + CF。如果DESTSRC都为零,则相当于DEST = CF。关于这一点的棘手部分是目标寄存器必须在进位标志置位之前预先归零,或者以进位标志不受影响的方式归零:

; Pre-zero AX in preparation for later use of ADC.
xor  ax, ax

; Perform operation that will set CF.
; (NOTE: Cannot modify AX register here, nor AL nor AH!)
...

; Set AX to the same value as CF.
adc  ax, ax
; Perform operation that will set CF.
...

; Zero AX in such a way that flags are not clobbered.
mov  ax, 0

; Set AX to the same value as CF.
adc  ax, ax

请注意,如果您想将CF的值存储在内存中,可以使用以下ADC形式:

adc  DWORD PTR [value], 0

在更现代的体系结构中,您可以使用SETCCMOVC指令(或SETNC / CMOVNC作为逆向逻辑 - 助记符与{相同{1}} / JC)。这些通常是编写无分支代码的更快的方法;但是,8086上都没有。{38}引入了Conditional set (SETcc) instructions,而Pentium Pro引入了conditional moves (CMOVcc)

答案 1 :(得分:1)

还有另一种方法可以检查CF是否已设置,而且是masking

这是一个有效的例子:

TITLE 'Check if Carray Flag is set or not'

.model small
.data
           cfMask equ 01h ; 01h because CF Flag is the least significant bit (right-most-bit) of the flag register
           cfMsg db 10,13,'CF: $'    
.code
.startup
           mov ax, @data
           mov ds, ax

           mov ah, 09h
           mov dx, offset cfMsg
           int 21h                    

           lahf ; Loads contents of flag register into ah

           and ah, cfMask ; Check if CF is set or not by anding it with 1

           mov dl, ah
           add dl, 48 
           mov ah, 02h           
           int 21h

           mov ah, 04ch
           int 21h
end

Flag register

答案 2 :(得分:1)

如果您想对其进行分支,请使用jc / jnc;这正是他们所要做的。

如果你希望CF的值为0/1整数:Cody的答案是好的,但还有其他一些方法。

如果emu8086支持the undocumented salc instruction,您可以使用它来设置al = 0 / -1。它是sbb al,al的单字节编码,并且在所有Intel和AMD CPU中都支持16和32位模式,包括Skylake和显然甚至是Knight's Landing(Xeon Phi) )。如果英特尔放弃对它的支持,SSSE3 / SSE4指令编码可能缩短一个字节。 (参见Agner Fog的博文Stop the instruction set war)。

但如果英特尔坚持支持它,我们也可以使用它,即使它们由于某种原因没有记录它。 (假设我们正在优化代码大小,即8086,而不是实际的现代CPU。salc是3 uops但是SnB / Haswell / Skylake的1c延迟)

salc            ; 1 byte  (but 3 uops on SnB-family)
and   al, 1     ; 2 byte special encoding of and al, imm8

; neg al       ; 2 bytes   also an option

或(@ Doda答案的优化版本):lahf效率很高。

lahf            ; 1 byte, 1 uop (SnB-family).  CF is the low bit of FLAGS.
and   ah, 1     ; 3 bytes

或者如果你想在其他地方使用0/1整数,例如所以你可以用@ {Doda答案)int 21h系统调用打印它:

lahf
mov   dx, 1       ; or just dl if you don't need to zero-extend it.
and   dl, ah

Upside vs. Cody&#39; sbb / neg:避免sbb same,same(2次uops,以及对除AMD Bulldozer系列以外的CPU上的寄存器的错误依赖)

下行:代码大小更差(真正的8086很重要)。更多说明。 (但在Broadwell之前,英特尔SnB系列的uop数量相同,其中sbb为2 uops)。写入dl然后在某些CPU(Intel pre-Ivybridge)上读取dx / edx时出现部分注册问题。

如果您不仅限于8086,请使用setc dl(或任何8位寄存器,或直接使用内存)。

也非常好,特别是在Broadwell和后来:来自@Cody的答案:

xor  eax,eax
; do something that sets CF
adc  eax, 0

adc没有setc al优势,if eax was xor-zeroed(避免部分注册停顿和合并对英特尔P6系列的处罚,而不是写al,然后读取eax)。