. . .
;Подпрограмма для деления значения на 10.
;Ввод: ax – значение, которое требуется разделить на 10
;Вывод: ax – значение, разделенное на 10
;dx – остаток значения, деленного на 10
DivideBy10 proc near
mov |
dx,0 |
; подготовить dx:ax как 32-битовое делимое |
mov |
bx,10 |
; bx – 16-битовый делитель (именно здесь bx:=10)! |
div bx ret
DivideBy10 endp codesg ends
В вызывающей программе подразумевается, что bx в процедуре DivideBy10 сохраняется, хотя фактически он устанавливается процедурой DivideBy10 в значение 10. В этом конкретном случае существует несколько возможных решений.
Например, способ 1 – сохранение регистра bx в вызывающей программе до вызова процедуры DivideBy10 и восстановление его тоже в вызывающей программе после вызова процедуры DivideBy10:
mov bx,1000 |
; bx :=1000 |
|
mov |
ax,w1 |
; ax:=w1=200 |
push bx |
; сохранить bx |
|
call |
DivideBy10 |
; разделить элемент на 10 |
pop |
bx |
; восстановить bx |
add |
bx,ax |
; вычисление суммы bx:=bx+ax=1000+w1/10 |
. . . |
; (в данном случае получится правильно) |
|
либо регистр bx можно загрузить после вызова процедуры (способ 2):
mov |
ax,w1 |
; ax:=w1=200 |
call |
DivideBy10 |
; разделить элемент на 10 |
mov bx,1000 |
; bx :=1000 |
|
add |
bx,ax |
; вычисление суммы |
или в начале процедуры DivideBy10 bx заносить содержимое регистра в стек, а при выходе из процедуры извлекать из стека (способ 3):
mov bx,1000 |
; bx :=1000 |
|
mov |
ax,w1 |
; ax:=w1=200 |
call |
DivideBy10 |
; разделить элемент на 10 |
add |
bx,ax |
; вычисление суммы |
. . .
;Подпрограмма для деления значения на 10.
;Ввод: ax – значение, которое требуется разделить на 10
;Вывод: ax – значение, разделенное на 10
81
; |
dx – остаток значения, деленного на 10 |
|
DivideBy10 proc near |
||
push bx |
; сохранить bx в стеке |
|
mov |
dx,0 |
; подготовить dx: ax как 32-битовое делимое |
mov |
bx,10 |
; bx –- 16-битовый делитель |
div |
bx |
|
pop |
bx |
; восстановить bx (извлечь из стека |
ret DivideBy10 endp
Первый подход к решению проблемы: все подпрограммы, которые могут изменять содержимое регистров, сохраняют их, а затем восстанавливают (требует дополнительного времени и увеличивает объем программы). Другой подход: каждая подпрограмма сопровождается комментарием, в котором указывается, какие регистры сохраняются, а какие разрушаются. При написании программы эта информация строго учитывается и тщательно проверяется, действительно ли в подпрограмме регистр не изменяется. Третий подход заключается в явном сохранении нужных регистров при вызове подпрограмм (см. уч. пос., ч.6).
5.6. Неопределенные символические имена [5, с.121-122]
Ассемблер генерирует сообщение о неопределенном символическом имени, если имя не определено в поле метки какой-либо строки ассемблерной программы. Если имя определено в другой программе (см. уч. пос., ч.2), то его следует определить как внешнее имя директивой extrn (external — внешний). Неправильная запись имени является наиболее вероятной причиной такой ошибки, особенно в тех случаях, когда используемые шестнадцатеричные константы записываются без начальной цифры.
secondproc near mov ax,cs mov ds,ax mov es,ax
;Неверные записи
;а) неопределенное имя value
mov |
ax,value |
; б) некорректная шестнадцатеричная запись |
|
mov |
bx,ffe2h; число ffe2h воспринимается как имя: нет нуля впереди |
;в) неопределенное имя code mov cx,code1
mov dx,code2 mov si,code
82
ret
code1 dw 100 code2 dw 200 code3 dw 300 secondendp
Ниже приведен образец правильной записи предыдущего примера. Символическое имя value представлено 16-битовым внешним именем. Шестнадцатеричная запись числа начинается с цифры, и неправильно записанное имя code заменено на правильное code3.
secondproc near extrn value:word mov ax,cs
mov ds,ax mov es,ax
mov ax,value |
|
mov bx,0ffe2h |
; т.к. первый символ– цифра, |
; |
воспринимается как число |
mov cx,code1 mov dx,code2 mov si,code3 ret
code1 dw 100 code2 dw 200 code3 dw 300 secondendp
5.7.Повторное определение символического имени [5, с.122-123]
Ассемблер формирует сообщение об ошибке, если одно и то же имя или метка два и более раз повторяется в поле метки ассемблерных строк программы.
case1:
decax ; ax:=ax-1 loop case1
and dx,bx cmp dx,0 jz case3
; Неверные записи
case1: ;имя case1 уже было определено
. . .
jz case3
83
Правильная запись предыдущего примера:
case1: |
|
|
dec ax |
; ax:=ax-1 |
|
; Неверные записи |
|
|
loop |
case1 |
|
and dx,bx |
|
|
cmp |
dx,0 |
|
jz case3 |
|
|
case2: |
|
|
. . .
jz case3
5.8. Неправильный порядок операндов
Многие программисты ошибаются и изменяют порядок операндов в инструкциях процессора 8086 на обратный. Это, вероятно, связано с тем, что строка
mov ax,bx
которая означает "поместить bx в ax", читается слева направо, и это иногда приводит к путанице. Чтобы запомнить порядок операндов в языке ассемблера процессора 8086, нужно строку
mov ax,bx
рассматривать как ax := bx
5.9.Неправильное использование операндов
Вязыке ассемблера первый операнд всегда используется в качестве приемника (кроме синтаксиса АТ&T для UNIX и Linux, см. уч. пос., ч.12). Операнд может быть записан в регистре общего назначения, регистре сегмента или ячейке памяти. При использовании непосредственных данных в качестве операнда-приемника генерируется сообщение об ошибке:
1) неправильная запись |
2) правильная запись |
cmp З,al |
cmp al,3 |
5.10. Потеря содержимого регистра при умножении
Большинство способов, с помощью которых программа на ассемблере может изменить состояние процессора, достаточно непосредственны и очевидны. Например, инструкция:
add bx,w1 |
значение по адресу w1 к bx и, чтобы отразить |
|
прибавляет |
16-битовое |
|
результат |
сложения, |
изменяет флаги переполнения, знака, нуля, |
|
|
84 |
дополнительного переноса, четности и переноса. Однако некоторые инструкции изменяют состояние процессора менее очевидным образом, и если программист забывает о необычных побочных эффектах, то возникает ошибка. Одной из таких ошибок является потеря содержимого регистра при умножении. Другие – такие как использование регистров ah, dx и edx (в МП 80386) после операции div и idiv в качестве старших разрядов частного, хотя содержат остаток и т.п., рассмотрены ниже (п.5.12-5.13).
В синтаксисе инструкций mul и imul явно указываются только один из операндов и размер, а регистры, используемые в качестве операндаприемника, просто подразумеваются. Это приводит к тому, что легко можно упустить из виду использование какого-либо неявного регистра. Есть много случаев, в которых, скажем, программист знает, что результат перемножения 16-разрядного значения на 16-разрядную величину, поместится в регистр ax. При этом часто забывают, что теряется содержимое регистра dx. Поэтому всегда нужно помнить о том, что при использовании инструкций mul и imul уничтожается содержимое не только регистров al, ax или eax (в МП 80386), но также и ah, dx или edx (в МП
80386).
5.11. Не подготовлены регистры при делении
Распространенной ошибкой для случаев, когда делимое по размеру совпадает с делителем и размещено в al или ax, является то, что программист не обнуляет регистры ah, dx.
Пример 1. Не очищается ah при делении байта на байт:
; нет обнуления ah или ax, т.е. пропущена команда xor ah,ah или xor ax,ax
mov al,b1 |
; al:=b1 |
div b2 |
; ax используется как делимое (но ah не подготовлен) |
mov byte ptr rezult,al
Пример 2. Не очищается dx при делении слова на слово. ; пропущена команда зануления dx, т.е. команда xor dx,dx
mov ax,w1 ;ax:=w1
div w2 ; dx:ax используется как делимое (но dx не подготовлен) mov word ptr rezult,ax
5.12. Неправильное использование регистра после деления
Не учитывается, что регистры ah и dx содержат остаток и используют в качестве старших разрядов частного.
Пример 1. Ошибочно используется ah после деления байта на байт: mov al,b1 ; b1/b2
div b2
mov word ptr rezult,ax ;ошибка: в ah содержится остаток,
85