Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29207
Скачиваний: 1689
Глава 5
Подпрограммы
5.1. Исправляем ошибку
Вероятно, те читатели, которые использовали в работе ассемблер TASM, столк-
нулись с проблемой при ассемблировании программ. TASM выдавал ошибку:
Near jump or call to different CS
В приложении 2 предлагался вариант решения проблемы путем вставки в код
программ строки вида
assume cs:CSEG
, после чего TASM ошибок при ассемблиро-
вании больше не выдает.
Так что же это за строка такая?
Дело в том, что оператор
assume
указывает ассемблеру, что необходимо привя-
зать сегментный регистр
cs
к сегменту
CSEG
. Сейчас попробуем разобраться.
MASM ассемблирует прекрасно и без этой строки. Если директива
assume
от-
сутствует, то MASM просто подразумевает ее по умолчанию и вставляет для себя
в процессе ассемблирования автоматически.
Другое дело TASM. Он, встретив в программе строки вида:
loop Label_1
jmp Label_2
call Procedure
не может "понять", с каким сегментным регистром следует связывать метку, и вы-
дает сообщение об ошибке.
Как уже упоминалось, мы пишем COM-файлы, в которых существует всего
один сегмент (мы называем его
CSEG
). Если вы создадите еще один и назовете его,
например,
DSEG
, то компоновщик (link.exe), при попытке создать COM-файл
(и только COM-файл!), выдаст ошибку. Чтобы полностью закрыть данную тему,
приведем полный вид разбираемой нами строки:
assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG
Этим мы "сообщаем" ассемблеру, что сегментные регистры
cs
,
ds
,
es
,
ss
будут
привязаны к нашему единственному сегменту
CSEG
. Возможно, это кажется слож-
ным на первый взгляд, но, написав несколько программ, вы полностью поймете
принцип. И мы будем стараться помочь вам.
Часть II. Усложняем задачи
48
5.2. Подпрограммы
В данном разделе рассмотрим возможности ассемблера при создании подпро-
грамм.
Мы уже вкратце рассматривали подпрограммы в части I. Сейчас затронем эту
тему более подробно.
Допустим, нам необходимо написать программу, которая выводит на экран со-
общение "Нажмите любую клавишу...", ждет нажатия клавиши, а затем выводит
еще одно сообщение: "Вы успешно нажали клавишу!", ждет, пока пользователь
нажмет любую клавишу, после чего завершает свою работу.
Что нужно для этого сделать? Вызвать два раза функцию
09h
прерывания
21h
и столько же функцию
10h
прерывания
16h
(листинг 5.1).
Листинг 5.1. Неоптимизированный вариант программы
...
(01) mov ah,9
(02) mov dx,offset Mess1
(03) int 21h
(04) mov ah,10h
(05) int 16h
(06) mov ah,9
(07) mov dx,offset Mess2
(08) int 21h
(09) mov ah,10h
(10) int 16h
(11) int 20h
...
(12) Mess1 db 'Нажмите любую клавишу...$'
(13) Mess2 db 'Вы успешно нажали клавишу!$'
...
П
Р И МЕ Ч А Н И Е
В тех местах, где стоит многоточие, опущен код программы. Это специально сделано
с той целью, чтобы постоянно не печатать очевидные операторы, тем самым нагру-
жая книгу бесполезным кодом. Так как мы написали и разобрали уже несколько про-
грамм, то проблем с пониманием целой структуры программы возникнуть не должно.
Строки (01)—(03) и (06)—(08) выводят сообщения. Они очень похожи, за ис-
ключением загрузки в регистр
dx
различных адресов строк. Вызов же функции
16h
в строках (04), (05) и (09), (10) полностью идентичен. Получается, что мы пишем
программу с точки зрения ассемблера неэффективно.
Глава 5. Подпрограммы
49
Чтобы упростить программу и, тем самым, уменьшить ее размер, воспользуемся
оператором
call
(табл. 5.1) и создадим подпрограммы (листинг 5.2).
Таблица 5.1. Оператор
call
Команда
Перевод
Назначение
Процессор
call метка
Call
— вызов
Вызов подпрограммы
8086
Листинг 5.2. Использование оператора call
...
(01) mov dx,offset Mess1
(02) call Out_string
(03) call Wait_key
(04) mov dx,offset Mess2
(05) call Out_string
(06) call Wait_key
(07) int 20h
...
(08) Out_string proc
(09) mov ah,9
(10) int 21h
(11) ret
(12) Out_string endp
(13) Wait_key proc
(14) mov ah,10h
(15) int 16h
(16) ret
(17) Wait_key endp
...
(18) Mess1 db 'Нажмите любую клавишу...$'
(19) Mess2 db 'Вы успешно нажали клавишу!$'
...
Не обращайте внимания на то, что второй вариант оказался длиннее. Подпро-
граммы (второе название — процедуры) используются в более сложных програм-
мах. Наш же вариант представлен исключительно для изучения.
Часть II. Усложняем задачи
50
Итак, что здесь происходит? Полагаем, что особых проблем в понимании кода нет.
Однако хотелось бы обратить внимание на некоторые моменты и добавить пояснения.
В строке (01) загружаем в
dx
адрес строки
Mess1
. В строке (02) вызываем под-
программу, которую мы назвали
Out_string
.
Что делает процессор в момент вызова подпрограммы? Он запоминает адрес
(смещение) следующей команды (строка (03)) и переходит на метку
Out_string
(строка (08)). Регистр
dx
при этом не изменяется, т. е. в нем сохраняется адрес
строки
Mess1
(рис. 5.1).
В строках (09), (10) используется функция
09h
прерывания
21h
для вывода
строки на экран. В строке (11) компьютер восстанавливает сохраненный адрес и
переходит на него, в данном случае на строку (03) (
ret
от англ. return — возврат).
Все, процедура отработала (рис. 5.2)!
Рис. 5.1. Вызов подпрограммы
Рис. 5.2. Процедура 0114h отработала
Глава 5. Подпрограммы
51
У вас, вероятно, возник вопрос: "А где это именно (в каком месте, области па-
мяти) компьютер запоминает адрес, куда нужно возвращаться после того, как про-
цедура отработала?" Ответ на этот вопрос не так прост, как кажется. Попробуем
ответить на этот вопрос вкратце, т. к. эта тема достойна отдельной главы.
Каждая программа отводит себе некую область памяти именно для подобных
целей, которая называется стеком (stack). Вот именно там и сохраняется адрес для
возврата из подпрограммы. В главе 6 мы подробно рассмотрим стек, его сегмент,
а также пару регистров
ss:sp
, которые отвечают за использование стека.
Надеемся, что разобраться в дальнейшей работе программы (начиная со строки
(03)) не составит труда. Единственное, на что следует обратить внимание —
оформление процедуры:
Out_string proc
...
Out_string endp
где:
Out_string
— название процедуры;
proc
(procedure) — процедура;
endp
(end procedure) — конец процедуры.
Стоит еще отметить, что из одной подпрограммы можно вызывать другие. Из дру-
гих — третьи. Главное правило — не запутаться в возвратах из подпрограмм, иначе
произойдет нарушение работы стека, и компьютер просто "зависнет"! Внимательно
следить за работой подпрограмм и использованием стека позволяет отладчик.
5.3. Программа для практики
Давайте попробуем усовершенствовать программу из главы 4, которая заполня-
ла весь экран веселыми рожицами (\005\prog05.asm). Усовершенствованный код
приведен в листинге 5.3.
Листинг 5.3. Практическое использование подпрограмм
(01) CSEG segment
(02) assume CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
(03) org 100h
(04) Start:
(05) mov ax,0B800h
(06) mov es,ax
(07) mov al,1
(08) mov ah,31
(09) mov cx,254
(10) Next_screen:
(11) mov di,0