Материал: 775

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

5.14. Неправильное использование регистров при преобразовании байта в слово и слова в двойное слово в командах cbw и cwd

Неправильное использование регистров при преобразованиях заключается либо в ошибочном размещении исходного значения, либо в использовании в качестве результата не регистров ax или (dx:ax). Для cbw и cwd исходные значения должны быть размещены соответственно в регистрах al и ax. Программисты же иногда ошибочно помещают исходное значение в какой-нибудь другой регистр (например, в bl или bx).

5.15.Применение команд преобразования cbw и cwd

кбеззнаковым данным

При применении команд cbw и cwd к беззнаковым данным может возникнуть ошибка. Когда знаковый бит равен единице, старший байт или слово, соответственно, заполняется единицами, а не нулями, как требуется в этом случае. Это надо учитывать при выполнении логических операций и операций сдвига и вместо инструкций cbw и cwd применять зануление старшего байта или слова командой xor. Например, если al= 128 (т.е. 100000002), то после выполнения команды cbw в регистре ax будет число 65408 (т.е. 11111111100000002), а должно остаться равным 128, но расширенным на 16 битов (т.е.

00000000100000002).

5.16. Изменение отдельными инструкциями флага переноса

Некоторые инструкции не влияют на те флаги, изменения которых вы ожидаете. Например, инструкция add в случае слишком большого для операнда-приемника результата устанавливает флаг переноса, а инструкция inc на флаг cf не влияет.

То же самое имеет место для инструкции dec, loop, loopz и loopnz (не влияют на состояние флагов; см. уч. пос., ч. 6 и 7). На практике это можно использовать в тех случаях, когда нужно выполнить одну из этих инструкций без нарушения установки флага переноса.

5.17. Программист долго не использует состояние флагов

Состояние флагов сохраняется до тех пор, пока следующая инструкция их не изменит. Обычно это не слишком долгий интервал времени. Хорошей практикой программирования является возможно скорейшее использование флагов их установки, что позволяет избежать многих потенциальных ошибок.

51

5.18. Ошибки при использовании регистров

При использовании регистров для записи операндов в ассемблерной программе необходимо соблюдать следующие правила:

1.Нельзя задавать 8- и 16-разрядные (а для процессора 80386 также 8-

и32-разрядные или 16- и 32-разрядные) регистры для записи двух операндов одной команды (это правило не распространяется на команды movsx и movzx, см. уч. пос., ч. 7).

2.Операции со стеком должны осуществляться только в 16-битовых кодах. Если 8-разрядный регистр указывают для работы со стеком (push/pop), то выдается сообщение об ошибке.

3.Сегментные регистры не могут прямо использоваться в арифметических вычислениях, логических операциях или для непосредственной передачи данных. Кроме того, сегментные регистры могут быть использованы только либо как источники операндов, либо как приемники операндов, но никак не одновременно (см. уч. пос., ч. 1).

5.19. Выход из диапазона адресов [5, с.124–125]

Все команды условного перехода используют режим относительной адресации с 8-битовым смещением. Если относительный адрес символа, используемого в качестве операнда-адреса перехода, выходит за диапазон +127 байтов -128 байтов от конца команды условного перехода, выдается сообщение об ошибке(см. уч. пос., ч. 1).

5.20. Программист забывает об инструкции ret

Правильный вызов подпрограммы состоит из вызова подпрограммы из другой части кода, выполнения подпрограммы и возврата из подпрограммы в вызывающую программу. Не забудьте включать в каждую подпрограмму инструкцию ret, по которой управление будет передаваться в вызывающий код. При наборе программы эту директиву легко пропустить и закончить код следующим образом:

;

;Подпрограмма для умножения значения на 80

;Ввод: ax – значение, которое нужно умножить на 80

;Вывод: dx:ax – произведение

MultiplyBy80 proc near mov dx,80

mul dx MultiplyBy80 endp

;Подпрограмма для получения следующей нажатой клавиши

;Вывод: al – следующая нажатая клавиша

;Содержимое регистра ah теряется

52

GetKey proc near mov ah,1 int 21h

ret GetKey endp

Директива MultipleBy80 endp может ввести в заблуждение: подпрограмма MultipleBy80 не завершена, т.е. после того, как содержимое ax будет умножено на 80, продолжается выполнение кода (в данном случае подпрограммы GetKey, и в регистре al будет возвращаться код следующей нажатой клавиши. Корректной эта подпрограмма будет в следующем виде:

;Подпрограмма для умножения значения на 80

;Ввод: ax – значение, которое нужно умножить на 80

;Вывод: dx:ax – произведение

MultiplyBy80

proc near

mov

dx,80

mul

dx

 

ret

 

; добавлена эта инструкция!

MultiplyBy80

endp

;Подпрограмма для получения следующей нажатой клавиши

;Вывод: al – следующая нажатая клавиша

;Содержимое регистра ah теряется

GetKey proc near mov ah,1 int 21h

ret GetKey endp

5.21. Генерация неверного типа возврата

Директива proc определяет имя, по которому будет вызываться процедура, и управляет типом (ближним или дальним) процедуры.

Тип процедуры используется ассемблером для определения того, какой тип вызовов нужно генерировать при вызове процедуры из того же исходного файла, и для определения типа инструкции ret, которая выполняется, когда процедура возвращает управление. Например:

;

; Подпрограмма ближнего типа для сдвига dx:ax вправо на 2 байта

LongShiftRight2 proc near

shr

dx,1

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

shr

dx,1

rcr

ax,1

; сдвиг dx:ax вправо еще на 1 бит

 

 

53

ret LongShiftRight2 endp

Ассемблер обеспечивает, что инструкция ret будет ближнего типа, так как LongShiftRight2 – это процедура ближнего типа (near). Однако, если директиву proc изменить следующим образом:

LongShiftRight2 proc far

то будет генерироваться инструкция ret дальнего типа (far).

Часто группируют в одной и той же процедуре несколько процедур. Поскольку в этих процедурах отсутствует соответствующая директива proc, их инструкции ret будут иметь тип той процедуры, в которую они заключены, а для конкретных подпрограмм этот тип не всегда может оказаться корректным. Например:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2 proc far

 

call

LongShiftRight

; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight

; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

LongShiftRight:

 

shr

dx,1

 

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

ret

 

 

LongShiftRight2 endp

работает неправильно. Процедура LongShiftRight2 обращается с вызовом ближнего типа к LongShiftRight (так как они находятся в одном сегменте кода). Однако, так как LongShiftRight встроена в процедуру

LongShiftRight2, то возврат в конце подпрограммы LongShiftRight

становится возвратом дальнего типа, а когда вызову ближнего типа соответствует возврат дальнего типа, это с большой вероятностью может привести к сбою (аварийному завершению программы).

Хорошим решением здесь будет наличие в каждой подпрограмме директивы proc. Вложенные директивы proc прекрасно работают:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2

proc far

 

call

LongShiftRight

; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight

; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

 

LongShiftRight

proc near

 

shr

dx,1

 

; сдвиг dx:ax вправо на 1 бит

rcr

ax,1

 

 

 

 

54

ret LongShiftRight2 endp LongShiftRight endp

Так же, как и последовательные процедуры:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2

proc far

call

LongShiftRight; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

LongShiftRight2

endp

LongShiftRight

proc near

shr

dx,1

 

rcr

ax,1 ; сдвиг dx:ax вправо на 1 бит

ret

 

 

LongShiftRight

endp

Для явной генерации ближнего или дальнего возврата можно использовать, соответственно, инструкции retn и retf. Вы можете обеспечить с их помощью корректную генерацию инструкций возврата.

5.22. Ошибки при использовании условных переходов

Использование в языке ассемблера инструкций условных переходов (je, jne, jc, jnc, ja, jb, jg и т.д.) обеспечивает большую гибкость в программировании, но при этом также очень просто ошибиться, выбрав неверный переход. Типичные ошибки:

1.Использование инструкций ja, jb, jae или jbe для сравнения значений со знаком или, соответственно, инструкций jg, jl, jge или jle для сравнения беззнаковых значений.

2.Использование инструкции ja там, где нужно использовать jae. Без буквы e в конце инструкции в сравнении не учитывается случай, когда два операнда равны.

3.Использование инвертированной логики, например, применение инструкции js там, где нужно использовать jns.

55

Источник: https://studfile.net/preview/16406832/