Как проверить, равен ли флаг 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