5.23. Перепутаны операнды в памяти и непосредственные операнды
Программа на ассемблере может ссылаться либо на переменную памяти по смещению, либо на значение, хранящееся в этой переменной.
Например, смещение переменной в памяти размером в слово MemLoc представляет собой константу 5002, которую можно получить с помощью оператора offset. Например, инструкция:
mov bx, offset MemLoc
загружает значение 5002h в регистр bx. Значение 5002h представляет собой непосредственный операнд. Другими словами, оно встроено непосредственно в инструкцию и не изменяется.
Допустим, значением MemLoc является 1234h. Оно считывается из памяти со смещением 5002h в сегменте данных. Один из способов считывания данного значения состоит в загрузке в регистр bx, si, di или bp смещения MemLoc и использования данного регистра для адресации к памяти. Инструкции:
mov bx, offset MemLoc mov ax,[bx]
загружают значение MemLoc в регистр ax. Значение MemLoc можно также загрузить непосредственно в ax с помощью инструкции:
mov ax,MemLoc
или
mov ax,[MemLoc]
Здесь значение 1234h получается как прямой, а не как непосредственный операнд: инструкция mov использует встроенное в нее смещение 5002h и загружает в ax значение по смещению 5002h, которое в данном случае равно 1234h.
Часто встречающейся ошибкой является то, что, увлекшись написанием программы, забывают использовать операцию offset, например:
mov si,MemLoc
где нужно использовать смещение MemLoc. На первый взгляд данная строка не выглядит неправильной, и так как MemLoc – это переменная размером в слово, то эта строка не приведет к ошибке ассемблирования. Однако при выполнении в si будут загружены содержащиеся в переменной MemLoc данные (1234h), а не ее смещение (5002h), и результаты будут непредсказуемы.
Надежного способа избежать этой проблемы нет, но можно принять за правило заключать все ссылки на память в квадратные скобки. Когда перед ссылками на адресные константы будет указываться префикс offset, а ссылки на память – заключаться в квадратные скобки, это устранит
56
двусмысленность и неопределенность при использовании имен переменных памяти. При таком соглашении работа инструкций:
mov si, offset MemLoc
и
mov si,[MemLoc]
становится совершенно понятной, в то время как инструкция mov si,MemLoc
будет настораживать.
5.2. Границы сегментов
Один из наиболее трудных моментов при программировании для процессоров 8086 заключается в том, что к памяти нельзя обращаться, как к одному большому массиву байт. Она доступна только по частям, каждая из которых равна 64 Кб и связана с сегментным регистром. Трудноуловимой ошибкой является обращение к адресу за концом сегмента, т.к. в действительности будет обращение к началу сегмента.
В качестве примера предположим, что память, начинающаяся с адреса 10000h, содержит некоторые данные. Когда регистр ds устанавливается в значение 1000h, программа, которая обращается к некоторой строке "Testing" по адресу 1000:FFF9, после обработки символа g по адресу 1000:FFFF, возвращается назад к байту по адресу 1000:0000, так как смещение не может превышать 0FFFFh (максимальное 16-битовое значение).
Предположим теперь, что при ds:si, равном 1000:FFF9, вызывается подпрограмма для преобразования строки "Testing" в верхний регистр:
; Подпрограмма для преобразования завершающейся нулевым символом
;строки в верхний регистр.
;Ввод: ds:si – указатель на строку.
ToUpper PROC NEAR
mov ax,[si] ; получить следующий символ
cmp |
al,0 |
; если 0, то |
jz ToUpperDone ;преобразовать строку |
||
cmp |
al,'a' |
; это строчная буква? |
jb |
ToUpperCase ; не строчная буква |
|
cmp |
al,'z' |
|
ja |
ToUpperNext ; не строчная буква |
|
and al,NOT 20h ; строчная буква, преобразовать ее в верхний регистр |
||
mov |
[si],al |
; сохранить прописную букву |
ToUpperNext:
57
inc |
si |
; ссылка на следующий символ |
jmp |
ToUpper |
|
ToUpperDone: ret
ToUpper endp
После того, как процедура ToUpper обработает первые семь символов строки, si изменит значение с 0FFFFh на 0 (si – это 16-разрядный регистр, поэтому отсчет с превышением значения 0FFFFh выполнить нельзя.) Завершающий строку нулевой байт, записанный по адресу 20000h, достигнут не будет. Вместо этого процедура начнет преобразовывать не относящиеся к делу байты по адресу 10000h и не остановится, пока не встретит нулевой байт. На более позднем этапе эти измененные байты могут вызвать некорректную работу программы. Часто такие ошибки, вызванные случайным изменением байтов при достижении программы конца сегмента, бывает очень трудно отследить, поскольку эти ошибки могут проявляться совсем в другом месте программы и в другое время. Рекомендация: нужно обеспечивать, чтобы программа непреднамеренно не выходила за конец сегмента, и не следует обращаться к слову с адресом 0FFFFh. Машина может "зависнуть".
Библиографический список
1.Зубков С.В. Assembler для DOS, Windows и UNIX.–3-е изд., стер.–
М.: ДМК Пресс; СПб.: Питер, 2005.– 608 с.
2.Бурдаев О.В., Иванов М.А., Тетерин И.И. Ассемблер в задачах защиты информации/ Под ред. И.Ю.Жукова. – М.: КУДИЦ-ОБРАЗ, 2002. – 320 с.
3.Абель Питер. Ассемблер и программирование для IBM PC. Британская Колумбия. Технологический институт.
4.Олейник Л.Е. Язык ассемблера для микропроцессора 8086: Курс лекций. – Омск: Сибирская региональная школа бизнеса, 2000.
5.Дао Л. Программирование микропроцессора 8088. – М.: Мир, 1988.
–356 с.
6.Абель Питер. Язык и программирование для IBM PC: Пер. с англ.– Киев: Век; М.: Энтроп; Киев: НТИ, 2003. – 736 с.
58
Вопросы для самопроверки
1.Какие шестнадцатеричные значения строки и столбца соответствуют нижнему правому углу экрана 25х40?
2.Напишите команды для установки курсора по координатам: строка 12, столбец 8.
3.Напишите команды для очистки экрана, начиная с 0-го столбца 12-й строки до 79-го столбца 22-й строки.
4.Составьте необходимые элементы данных и команды для вывода запроса 'Введите дату (дд/мм/гг)'. За сообщением должен следовать звуковой сигнал. Используйте для вывода функцию базовой версии DOS.
5.Составьте необходимые элементы данных и команды для ввода с клавиатуры в формате дд/мм/гг. Используйте для ввода функцию базовой версии DOS.
6.Укажите стандартные файловые номера для ввода с клавиатуры, обычного вывода на экран и вывода на принтер.
7.Определите атрибуты экрана:
а) для мигания с подчеркиванием; б) нормальной яркости;
в) инвертирования с выделением яркостью.
8.Составьте процедуру для установки режима экрана BW (черно-белый) на 80 столбцов
9.Составьте процедуру для прокрутки экрана на 10 строк.
10.Понятие «модуль программы».
11.Назначение директив extrn, public и global.
12.Назначение команды and.
13.Назначение команды or.
14.Назначение команды xor.
15.Назначение команды not.
16.Операция «логическое И», электрическая схема, реализующая эту операцию, таблица истинности.
17.Операция «логическое ИЛИ», электрическая схема, реализующая эту операцию, таблица истинности.
18.Операция «исключающее ИЛИ», таблица истинности.
19.Операция «логическое НЕ», таблица истинности.
59
6. ПРИМЕРЫ (ТЕКСТЫ) ПРОГРАММ
Пример 2.1
;l_2a#.asm EXE-программа. Модуль 1-й "Вызов процедуры умножения" ; ред. 05_01_06
extrn submul:far
;-----------------------------------------------
stacksg segment para Stack 'Stack' dw 64 dup (?)
stacksg ends
datasg segment para 'Data'
qty |
dw 0002h |
;количество |
price |
dw 0004h |
;стоимость |
msg |
db '0',0dh,0ah,'$' |
|
datasg ends
codesg segment para 'Code'
assume cs:codesg,ds:datasg,ss:stacksg
begin proc far |
|
push ds |
|
xor ax,ax |
|
push ax |
|
mov ax,datasg |
|
mov ds,ax |
|
mov ax,price |
;ax:= стоимость (price) |
mov bx,qty |
;bx:= количество (qty) |
call submul |
;вызвать подпрограмму submul (внешнюю) |
;результат возвращается в регистрах (dx:ax)
;в частном случае (для заданных данных), т.е. когда получается
;одноразрядное десятичное число, предусмотрим вывод результата на экран
or al,30h |
; преобразование в код ASCII |
mov msg,al |
|
mov ah,09h |
; вывод на экран полученного значения |
lea dx,msg |
|
int 21h |
|
ret |
|
begin endp |
|
codesg ends |
|
end begin |
|
;----------------------------------------------- |
|
;l_2b#.asm EXE-программа. Модуль 2-й "Процедура умножения" codesg segment para 'Code'
submul proc far assume cs:codesg public submul
;параметры процедуре передаются в регистрах ax и bx
– первая цифра в номере примера (до точки) обозначает номер части учебного пособия «Языки программирования»
60