 |
.CODE
ProgramStart PROC NEAR
mov ax,@Data
mov ds,ax
mov bx,OFFSET Message1
call PrintString ; вывести строку "Привет"
mov bx,OFFSET Message2
call PrintString ; вывести строку "Пример строки"
mov bx,OFFSET Message3
call PrintString ; вывести строку "Еще одна
; строка"
mov ax,4ch ; функция DOS завершения
; программы
int 21h ; завершить программу
ProgramStart ENDP
;
; Подпрограмма вывода на экран строки, завершающейся
; нулевым символом
;
; Входные данные:
; DS:BX - указатель на выводимую строку.
;
; Нарушаемые регистры: AX, BX
;
PrintString PROC NEAR
PrintStringLoop:
mov al[bx] ; получить следующий символ
; строки
and dl,dl ; значение символа равно 0?
jz EndPrintString ; если это так, то вывод
; строки завершен
inc bx ; ссылка на следующий
; символ
mov ah,2 ; функция DOS вывода символа
int 21h ; вызвать DOS для вывода
; символа
jmp PrintStringLoop ; вывести следующий символ,
; если он имеется
EndPrintString:
ret ; возврат в вызывающую
; программу
PrintString ENDP
END ProgramStart
Здесь стоит отметить два момента. Во-первых, подпрограмма
PrintString не настроена жестко на печать определенной строки.
Она может печатать любую строку, на которую с помощью BX укажет
вызывающая программа. Во-вторых, для выделения подпрограммы ис-
пользованы две новых директивы - PROC и ENDP.
Директива PROC используется для того, чтобы отметить начало
процедуры. Метка, указанная в директиве PROC (в данном случае -
PrintString), представляет собой имя процедуры, как если бы ис-
пользовалось:
PrintString LABEL PROC
Однако, директива PROC делает большее. Она определяет, какую
инструкцию RET (возврат управления) - ближнюю или дальнюю - сле-
дует использовать в данной процедуре.
Давайте рассмотрим последний оператор несколько подробнее.
Вспомним, что когда выполняется переход на метку ближнего типа
(NEAR), в IP загружается новое значение, а при переходе на даль-
нюю метку (FAR) новые значения загружаются и в регистр IP, и в
CS. Если инструкция CALL ссылается на дальнюю метку, загружаются
и CS, и IP (как и при переходе).
Вот почему при дальнем вызове в стек заносятся и регистр CS,
и IP. Иначе откуда инструкция RET получит достаточную информацию
для возврата в вызывающую программу? Ведь если дальний вызов заг-
рузит CS и IP, а занесет в стек только IP, то при возврате можно
будет только загрузить IP из вершины стека. Тогда в результате
выполнения инструкции RET пара регистров CS:IP содержала бы зна-
чение CS вызываемой программы, а IP - вызывающей, что очевидно не
имеет смысла.
Что же произойдет, когда в стек будут занесены оба регистра
- CS и IP? Как Турбо Ассемблер узнает о типе возврата, генерируе-
мом в соответствующей подпрограмме? Один из путей состоит в явном
задании типа каждой инструкции возврата - RETN (возврат ближнего
типа) или RETF (возврат дальнего типа), однако лучший способ зак-
лючается в использовании директив PROC и ENDP.
Директива ENDP используется для того, чтобы пометить конец
подпрограммы, начатой с помощью директивы PROC. Директива ENDP
отмечает конец подпрограммы, которая начинается с директивы PROC
с той же меткой. Например, директивы:
.
.
.
TestSub PROC NEAR
.
.
.
TestSub ENDP
.
.
.
отмечают начало и конец подпрограммы TestSub.
Директивы ENDP и PROC не генерируют выполняемого кода, ведь
это директивы, а не инструкции. Все их действие заключаются в
управлении типом инструкции RET данной подпрограммы.
Если операндом директивы PROC является NEAR (ближний), то
все инструкции RET между директивой PROC и соответствующей ди-
рективой ENDP ассемблируются, как возвраты управления ближнего
типа. Если же, с другой стороны, операндом директивы PROC являет-
ся FAR (дальний), то все инструкции RET в данной процедуре ассем-
блируются, как возвраты управления дальнего типа.
Поэтому, чтобы, например, изменить тип всех инструкций RET в
TestSub, измените директиву PROC следующим образом:
TestSub PROC FAR
В общем случае лучше там, где это возможно, использовать
подпрограммы ближнего типа, так как дальние вызовы занимают боль-
ше памяти и выполняются медленнее, а возвраты дальнего типа также
выполняются медленнее, чем ближнего. Однако подпрограммы дальнего
типа становятся необходимыми, когда объем кода вашей программы
превышает 64К.
Если вы используете упрощенные директивы определения сегмен-
тов, то лучше использовать директиву PROC без операндов, напри-
мер:
TestSub PROC
Когда Турбо Ассемблер встречает такую директиву, он автома-
тически настраивает ее тип в соответствии с выбранной с помощью
директивы .MODEL моделью памяти (по умолчанию это малая модель
памяти). Программы со сверхмалой, малой и компактной моделями па-
мяти могут иметь вызовы ближнего типа, а вызовы в программах со
средней, большой и сверхбольшой моделью памяти имеют дальний тип.
Например, в программе:
.
.
.
.MODEL SMALL ; малая модель памяти
.
.
.
TestSub PROC
.
.
.
подпрограмма TestSub вызывается с помощью ближнего вызова, а в
программе:
.
.
.
.MODEL LARGE ; большая модель памяти
.
.
.
TestSub PROC
.
.
.
с помощью дальнего.
Передача параметров
-----------------------------------------------------------------
Из программ, вызывающих подпрограммы (которые называют вызы-
вающими программами или вызывающим кодом), подпрограммам часто
передается информация. Например, в примере программы предыдущего
раздела для передачи в подпрограмму PrintString использовался ре-
гистр BX. Это действие называется передачей параметров. При этом
параметры указывают подпрограмме, что нужно сделать.
Существует два общепринятых способа передачи параметров: в
регистрах и в стеке. Передача параметров через регистры часто ис-
пользуется в чистом коде Ассемблера, а передача через стек ис-
пользуется в большинстве языков высокого уровня, включая Паскаль
и Си, и в подпрограммах на Ассемблере, вызываемых из этих языков.
Передача параметров в регистрах очень проста. Для этого нуж-
но просто поместить значения-параметры в соответствующие регистры
и вызвать подпрограмму. Каждая подпрограмма может иметь свои
собственные потребности в параметрах, хотя вы вероятно поймете,
что чтобы избежать путаницы, лучше выработать некоторые соглаше-
ния и придерживаться их. Например, вы можете следовать правилу,
согласно которому первый параметр-указатель всегда передается в
регистре BX, второй - в SI и т.д. Если вы используете для переда-
чи параметров регистры, аккуратно комментируйте каждую подпрог-
рамму - какие параметры она получает и в каких регистрах они
должны находиться.
Передача параметров в стеке несколько более сложна и отлича-
|