Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ПСМПС (лаб)

.pdf
Скачиваний:
20
Добавлен:
10.06.2015
Размер:
1.42 Mб
Скачать

51

. . .

showmessage proc push AX push DX

mov AH, 09h

mov DX, offset message int 21h

pop DX pop AX ret

showmessage endp

. . .

end main

Промежуточный вариант расположения тела процедуры предполагает ее размещение внутри другой процедуры или основной программы. В этом случае необходимо предусмотреть обход тела процедуры, ограниченного директивами PROC и ENDP, с помощью команды безусловного перехода JMP.

Поскольку процедура обладает такими же атрибутами, что и обычная метка, то обратиться к процедуре можно с помощью любой команды перехода. Однако имеется существенное отличие, которое можно использовать с помощью специального механизма вызова процедур. Суть этого механизма состоит в возможности сохранения контекста программы в точке вызова процедуры, что и делает возможным возврат в эту точку после выполнения подпрограммы. Под контекстом понимается информация о состоянии программы в точке вызова процедуры. В системе команд микропроцессора имеется две команды, которые осуществляют работу с контекстом. Это инструк-

ции CALL и RET.

Инструкция CALL осуществляет вызов подпрограммы и имеет следующий формат:

call [<modifier>] <address>

Подобно инструкции JMP, данная инструкция передает управление по адресу <address>, но при этом в стеке сохраняется адрес возврата – адрес команды, следующей за командой CALL.

Инструкция RET возвращает управление вызывающей программе:

52

ret [<number>]

Команда RET считывает адрес возврата из стека и загружает его в регистры CS и IP/EIP, тем самым возвращая управление на команду, следующую в программе за инструкцией CALL. Параметр <number> не является обязательным и обозначает количество элементов, удаляемых автоматически из стека при возврате из процедуры.

Так же, как и в случае инструкции JMP, вызовы с помощью команды CALL могут быть ближними внутрисегментными и дальними межсегментными. При внутрисегментном вызове параметр <modifier> имеет значение NEAR, и в качестве адреса возврата команда CALL сохраняет только содержимое IP/EIP, что вполне достаточно для осуществления возврата. Напротив, если вызываемая процедура находится в другом сегменте (вызов с модификатором FAR), то для осуществления возврата команда CALL сохраняет в стеке регистры CS и IP/EIP. Очередность размещения их в стеке такова: сна-

чала CS , затем IP/EIP.

2.1.2. Прохождение параметров в процедуры. Локальные переменные процедуры

Процедуры могут получать или не получать параметры из вызывающей процедуры и могут возвращать или не возвращать результаты (процедуры, которые что-либо возвращают, называются функциями в языке Pascal и C, но ассемблер не делает каких-либо различий между ними).

Значения параметров могут быть переданы в подпрограмму с указанием значения или адреса ячейки, где находится значение. С этой точки зрения параметры можно передавать с помощью одного из трех механизмов:

по значению;

по ссылке;

по возвращаемому значению.

С точки зрения локализации передаваемых параметров (то, где они будут находится) можно использовать следующие объекты:

регистры общего назначения;

глобальные переменные (общедоступные ячейки памяти модуля);

стек программы.

В общем случае, используя различные комбинации способов передачи параметров в ассемблере, можно реализовать до 30 различных разновидностей передачи параметров.

53

Простейшим типом передачи параметра в подпрограмму является передача по значению (by value). Процедуре передается собственно значение параметра. При этом фактически значение параметра копируется, и процедура использует его копию, так что модификация исходного параметра оказывается невозможной. Этот механизм применяется для передачи небольших параметров, таких, как одиночные байты или слова. Например, если параметры передаются в регистрах:

mov ax, word ptr value

;

сделать

копию значения

call myproc

;

вызвать

процедуру

Более эффективным способом является передача параметров по ссылке (by reference), т.е. с указание адреса параметра. Процедуре при этом передается не значение переменной, а ее адрес, по которому процедура должна сама прочитать значение параметра. Этот механизм удобен для передачи больших массивов данных и в тех случаев, когда процедура должна модифицировать параметры, хотя он и медленнее из-за того, что процедура будет выполнять дополнительные действия для получения значений параметров. Например:

mov ax, offset value call myproc

Комбинированным способом передачи параметров, на основе двух предыдущих является способ передачи параметров по возвращаемому значению (by return value). Этот механизм объединяет передачу по значению и по ссылке. Процедуре передают адрес переменной, а процедура делает локальную копию параметра, затем работает с ней, а в конце записывает локальную копию обратно по переданному адресу. Этот метод эффективнее обычной передачи параметров по ссылке в тех случаях, когда процедура должна обращаться к параметру достаточно большое число раз, например, если используется передача параметров в глобальной переменной:

mov global_variable,offset value call myproc

...

myproc proc near

mov dx, global_variable mov ax, word ptr [dx]

...

54

; команды, работающие с АХ в цикле, большое число раз

...

mov word ptr [dx],ax

Если процедура получает небольшое число параметров, идеальным местом для их передачи оказываются регистры. Примерами использования этого метода могут служить практически все вызовы прерываний DOS и BIOS. Языки высокого уровня обычно используют регистр АХ (ЕАХ) , для того чтобы возвращать результат работы функции.

Когда не хватает регистров, один из способов обойти это ограничение – записать параметр в переменную, к которой затем обращаться из процедуры. Этот метод считается неэффективным, и его использование может привести к тому, что рекурсия (recursion) и повторная входимость (reentrance реенте-

рабельность) станут невозможными.

Наиболее эффективным и универсальным способом размещения передаваемых в процедуры параметров является их передача через стек программы. Параметры помещаются в стек сразу перед вызовом процедуры. Именно этот метод используют языки высокого уровня, такие, как Си и Pascal. Для чтения параметров из стека обычно используют не команду POP, а регистр ВР (рис. 4.2.), в который помещают адрес вершины стека после входа в процедуру (необходимо учитывать, что стек растет из области старших в область младших адресов памяти):

push param1

;

поместить параметр

в стек

push param2

 

 

 

call myproc1

 

 

 

add SP,4

;

освободить стек от

параметров

...

myproc1 proc near push BP

mov BP,SP

; команды, которые могут использовать стек mov AX,[BP+4] ; считать параметр 2.

;Его адрес в сегменте стека ВР + 4,

;потому что при выполнении

;команды CALL в стек поместили

;адрес возврата - 2 байта для

;процедуры типа NEAR(или 4 - для FAR),

;а потом еще и ВР - 2 байта

55

mov BX,[BP+6] ; считать параметр 1 ; остальные команды

рор BP ret

myproc1 endp

 

«Дно» стека

 

+6

param1

push param1

 

push param2

param2

+4

 

call myproc1

IP

+2

BP

push BP

[BP] 0

 

 

mov BP, SP

 

 

Рис. 4.2. Стек программы до и после вызова процедуры myproc1

Параметры в стеке, адрес возврата и старое значение ВР вместе называются активационной записью процедуры (функции).

Для удобства ссылок на параметры, переданные в стеке, внутри функции иногда используют директивы EQU, чтобы не писать каждый раз точное смещение параметра от начала активационной записи (то есть от ВР), например так:

push X push Y push Z

call myproc2

...

myproc proc near myproc2_z equ [BP+8] myproc2_y equ [BP+6] myproc2_x equ [BP+4] push BP

mov BP,SP

;команды, которые могут использовать стек mov AX, myproc_x ;считать параметр X

;остальные команды

pop BP

56

ret 6 myproc2 endp

При анализе этого метода передачи параметров возникает сразу два вопроса: кто должен удалять параметры из стека – процедура или вызывающая ее программа, и в каком порядке помещать параметры в стек. В обоих случаях оказывается, что оба варианта имеют свои «за» и «против», так, например, если стек освобождает процедура (командой RET n), то код программы получается меньшим, а если за освобождение стека от параметров отвечает вызывающая функция, как в нашем примере, то становится возможным вызвать несколько функций с одними и теми же параметрами просто последовательными командами CALL. Первый способ, более строгий, используется при реализации процедур в языке Pascal, а второй, дающий больше возможностей для оптимизации, – в языке Си. Разумеется, если передача параметров через стек применяется и для возврата результатов работы процедуры, из стека не надо удалять все параметры, но популярные языки высокого уровня не пользуются этим методом. Кроме того, в языке Си параметры помещают в стек в обратном порядке (справа налево), так что становятся возможными функции с изменяемым числом параметров (как, например, printf – первый параметр, считываемый из [ВР+4], определяет число остальных параметров).

Часто процедурам требуются локальные переменные, которые не будут нужны после того, как процедура закончится. По аналогии с методами передачи параметров можно говорить о локальных переменных в регистрах – каждый регистр, который сохраняют при входе в процедуру и восстанавливают при выходе, фактически играет роль локальной переменной. Единственный недостаток регистров в роли локальных переменных – их слишком мало.

Наиболее распространенный способ хранения локальных переменных в процедуре – хранение в стеке. Принято располагать локальные переменные в стеке сразу после сохраненного значения регистра ВР, так что на них можно ссылаться изнутри процедуры, как на адреса [ВР-2], [ВР-4], [ВР-6] и т.д.:

myproc3 PROC NEAR

param_a equ [BP+8] ; параметры param_b equ [BP+6]

param_c equ [BP+4]

var_x equ [BP-2] ; локальные переменные var_y equ [BP-4]

 

 

57

 

 

push BP

; сохранить предыдущий ВР

 

mov BP,SP

;

установить ВР для

этой процедуры

sub SP,4

;

зарезервировать 4

байта

для

;локальных переменных

;тело процедуры

mov SP,BP

; восстановить

SP, выбросив

pop BP

; из стека все

локальные переменные

;

восстановить

ВР вызвавшей процедуры

ret 6

;

вернуться, удалив параметры из стека

myproc3 ENDP

Внутри процедуры myproc3

стек будет заполнен так, как показано на

рис. 4.3.

 

 

 

 

 

 

 

param_a

 

 

 

param_b

 

 

 

 

 

 

 

 

 

 

param_c

 

 

 

 

 

 

IP

 

 

 

 

 

 

 

BP

 

[BP]

 

 

 

var_x

 

 

 

var_y

 

[SP]

Рис. 4.3. Стек при вызове процедуры myproc3

Последовательности команд, используемые в начале и в конце таких процедур, оказались настолько часто применяемыми, что в процессоре 80186 были введены специальные команды ENTER и LEAVE, выполняющие эти же самые действия:

myproc3 PROC NEAR

; параметры

param_a equ [bp+8]

param_b

equ

[bp+6]

 

param_c

equ

[bp+4]

 

var_x equ [bp-2]

; локальные переменные

 

58

var_y equ [bp-4]

 

enter 4,0

; push bp

 

; mov bp,sp

 

; sub sp,4

; тело процедуры

 

leave

; mov sp,bp

ret 6

; pop bp

; вернуться,

 

; удалив параметры

myproc3 ENDP

; из стека

 

Область в стеке, отводимая для локальных переменных вместе с активационной записью, называется стековым кадром (stack frame).

2.2. Макросредства ассемблера

Одно из самых мощных языковых средств ассемблера – макроопределения. Макроопределением (или макросом) называется участок программы, которому присвоено имя и который ассемблируется всякий раз, когда ассемблер встречает это имя в тексте программы. Макрос начинается директивой MACRO и заканчивается ENDM.

<name> MACRO [<formalparam_list>] <statements>

ENDM

Для того чтобы код макроопределения был развернут в заданной точке, в программе должно быть использовано имя макроса подобно инструкции. Указание имени макроса называется макроподстановкой. Если макрос имеет список параметров, то при макроподстановке также должны быть указаны фактические параметры, в качестве которых могут выступать литеральные значения, имена регистров, символов и др.

<name> [<actualparam_list>]

Макроопределения должны располагаться до первого упоминания имени макроса, то есть до первой макроподстановки. Как правило, макроопределения указывают в начале программы до сегмента данных и кода, чтобы не ухудшать читабельность программы. Этот вариант размещения макросов следует применять в тех случаях, если определяемые макрокоманды ак-

59

туальны только в пределах одной этой программы. Если макросы имеют значение для нескольких программных проектов, то их целесообразно разместить в отдельном файле. Чтобы использовать эти макроопределения в конкретной программе, необходимо в начале исходного текста программы записать директиву INCLUDE с указанием имени файла с макросами:

include < filename>

Для макросов универсального назначения можно создать макробиблиотеку и использовать макроопределения из нее в программных проектах. Недостаток использования больших макробиблиотек состоит в том, что в исходный текст программы включаются абсолютно все макроопределения. Для устранения этого недостатка можно использовать директиву PURGE. В качестве ее операндов через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы.

Рассмотрим пример простого макроопределения:

;макрос вывода строки на экран outstring macro str

push AX mov AH, 09h

mov DX, offset str in 21h

pop AX

endm

;макрос настройка сегментного регистра на сегмент данных initdataseg macro datareg, dataseg

mov AX, nameseg mov datareg, AX

endm

;макрос завершения программы

exitprog macro mov AX, 4C00h int 21h

endm

60

Данное макроопределение может быть использовано в программе следующим образом:

text segment ‘code’

assume CS: text, DS: data initdataseg DS, data

. . .

outstring message1

. . .

outstring message2

. . .

exitprog text ends data segnent

message1 db 'Hello world$' message2 db ’Good-bye world$'

data ends

end

Как видно из примеров, по задачам, решаемым с помощью макроопределений, они подобны процедурам. Сходство их в том, что и те, и другие позволяют существенным образом сократить объем кодирования. Однажды описанная процедура или макроопределение могут быть специальным образом вызваны в любой точке кода.

Вотличие от процедуры, текст которой неизменен, макроопределение

впроцессе макрогенерации может меняться в соответствии с набором фактических параметров. При этом коррекции могут подвергаться как операнды команд, так и сами команды. Процедуры в этом отношении менее гибкие. Однако макрокоманда имеет недостаток. Он связан с тем, что при каждом вызове макрокоманды ее текст в виде макрорасширения вставляется в программу. Напротив, при вызове процедуры микропроцессор осуществляет передачу управления на начало процедуры, находящейся в некоторой области памяти в одном экземпляре. В этом случае бинарный код исполняемого модуля получается намного компактнее, хотя быстродействие программы несколько снижается за счет необходимости осуществления переходов.

Спомощью макросредств ассемблера можно не только частично изменять входящие в макроопределение строки, но и модифицировать сам набор этих строк и даже порядок их следования. Эта возможность предоставляется