Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29207

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

 

 

 

Глава 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

. Возможно, это кажется слож-

ным  на  первый  взгляд,  но,  написав  несколько  программ,  вы  полностью  поймете 
принцип. И мы будем стараться помочь вам. 


background image

 

Часть 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) полностью идентичен. Получается, что мы пишем 
программу с точки зрения ассемблера неэффективно. 


background image

Глава 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 'Вы успешно нажали клавишу!$' 
... 

Не  обращайте  внимания  на  то,  что  второй  вариант  оказался  длиннее.  Подпро-

граммы  (второе  название —  процедуры)  используются в более сложных програм-
мах. Наш же вариант представлен исключительно для изучения. 


background image

 

Часть 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 отработала 


background image

Глава 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