Как проверить, равен ли флаг CF 1 в emu8086?

Я пытаюсь определить, равен ли флаг CARRY единице или нет, но я не знаю, как это проверить. Я написал приведенный ниже код, но мне нужна помощь с вопросительными знаками, которые я вставил.

  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 ответа

Вы не можете использовать CMP инструкции, так как флаги не являются допустимыми операндами для команд x86. Они неявно используются только определенными инструкциями.

Самое простое решение - просто использовать условную ветвь. Это работает аналогично JE инструкция, с которой вы уже знакомы, за исключением того, что она ветвится на основе значения флага переноса (CF) вместо флага нуля (ZF), например JE делает.

Чтобы условно перейти на статус флага переноса (CF), вы должны использовать JC или же JNC, JC будет разветвляться, если установлен флаг переноса (CF == 1), тогда как JNC будет разветвляться, если флаг переноса не установлен (CF == 0). Мнемоника для этих кодов операций - просто "Jmp, еслиC arry" и "Jmp, если N arC".

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)

Но, как отмечает Питер Кордес в комментарии, ваш код почти наверняка неверен, поскольку идентичный код будет выполнен независимо от того, будет ли выполнена ветвь. Другими словами, назначение ветви эквивалентно резервному коду. Вы, вероятно, хотите иметь что-то вроде:

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 инструкция Если оба операнда являются одним и тем же регистром, то этот регистр будет установлен в -1, если установлен флаг переноса (CF == 1), или в этот регистр в 0, если флаг переноса не установлен (CF == 0). Это работает, потому чтоSBBна самом деле выполняет операцию DEST = (DEST - (SRC + CF)), когдаDEST а такжеSRCодинаковое значение, это эквивалентно DEST = -CF,

В тех случаях, когда было бы удобнее иметь точное значение зеркала CF регистра, это можно объединить с NEG инструкция:

; 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 Инструкция аналогичным образом. Это выполняет операциюDEST = DEST + SRC + CF, когда DESTа такжеSRC оба равны нулю, это эквивалентно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

На более современных архитектурах вы можете использовать либоSETCили жеCMOVCинструкции (илиSETNC/CMOVNCдля обратной логики - мнемоника такая же, как JC / JNC). Как правило, это даже более быстрые способы написания кода без ответвлений; однако ни один не доступен на 8086. Условный набор ( SETcc ) инструкции были введены с 386, а условные ходы ( CMOVcc ) были представлены с Pentium Pro.

Если вы хотите перейти на него, используйте jc / jnc; это именно то, для чего они.

Если вы хотите, чтобы значение CF представляло собой целое число 0 / 1: ответ Коди хорош, но есть и другие способы.

Если emu8086 поддерживает недокументированное salc инструкция, вы можете использовать его для установки al = 0 / -1. Это однобайтовая кодировка sbb al,al и поддерживается в 16- и 32-битном режиме всеми процессорами Intel и AMD, включая современные, такие как Skylake и, по-видимому, даже Knight's Landing (Xeon Phi). Кодировки инструкций SSSE3 / SSE4 могли бы быть на один байт короче, если бы Intel отказалась от их поддержки. (См. Пост в блоге Агнера Фога " Остановите войну с набором команд").

Но если Intel настаивает на его поддержке, мы могли бы также использовать его, даже если они по какой-то причине не документируют его. (Предполагая, что мы оптимизируем для размера кода, то есть для 8086, а не для реального современного процессора. salc составляет 3 мопа, но 1 с задержкой на SnB/Haswell/Skylake)


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

Или (оптимизированная версия ответа @ Доды): lahf является эффективным.

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

Или если вы хотите, чтобы целое число 0 / 1 где-то еще, например, чтобы вы могли напечатать его с int 21h системный вызов как ответ @ Доды):

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

Перевернутый против Коди sbb / neg: избегает sbb same,same (2 мопа и ложная зависимость от регистра на процессорах, отличных от семейства AMD Bulldozer)

Недостаток: худший размер кода (очень важно для настоящего 8086). Больше инструкций. (Но такое же количество мопов на Intel SnB-семействе до Broadwell, где sbb 2 мопс). Проблемы с частичной регистрацией при записи dl и последующем чтении dx / edx на некоторых процессорах ( Intel pre-Ivybridge).


Если вы не ограничены только 8086, используйте setc dl (или любой 8-битный регистр, или напрямую в память).

Также очень хорошо, особенно на Бродвелле и позже: из ответа @ Коди:

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

adc не имеет никакого преимущества перед setc al хотя, если eax был обнулен по xor (исключает частичные задержки регистрации и слияния штрафов на семействе Intel P6 от написания al и последующего чтения eax).

Есть еще один способ проверить, установлен CF или нет, и это с маскировкой.

Вот рабочий пример:

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

Флаг регистр

Другие вопросы по тегам