|
|
|
|
Передача данных в стек
Передача данных в стек является способом, используемым большинством компиляторов языков высокого уровня для реализации вызова процедур. При
этом способе передачи данных перед выполнением вызова все требуемые параметры заносятся в стек. После вызова вызывающая программа осуществляет доступ к
данным без их пересылки. Проектировщики семейства микропроцессоров 8086 поддержали этот способ при обеспечении регистра BP (base pointer - указатель
базы). Регистр BP имеет удивительную особенность адресации его операндов относительно сегмента стека. Это означает, что при установке значения регистра
BP в правильное положение, содержимое стека может быть адресовано путем использования индексной адресации.
Что же такое "правильное положение" при загрузке в BP? Это не сам SP (stack pointer - указатель стека), поскольку SP указывает на адрес возврата в
стек. Данные обычно начинаются с ячейки SP+2 или ячейки SP+4. Почему плюс 2, или плюс 4? Потому, что для вызова процедуры near (близкий) процессор
запоминает только текущее смещение (указатель инструкции) в стеке (2 байта), в то время как для вызова процедуры far (далекий) процессор запоминает
смещение и сегмент программы в стеке (4 байта). Вызываемая программа может быть закодирована для начала доступа в правильном положении (в зависимости
от типа программы) при использовании следующей адресации:
NEAR FAR
mov bp,sp mov bp,sp
mov <1-й аргумент>,[bp+2]
mov <1-й аргумент>,[bp+4]
... ...
|
Заметим, что если необходимо сохранить содержимое регистра BP, то обычно в этом случае вызываемая программа должна поместить BP в стек, изменив
адрес первого параметра на [BP+4] для программы near и на [BP+6] для программы far. Чтобы избежать это изменение адресов, необходимо перед тем, как
поместить параметры в стек, передать вызывающей программе ответственность за сохранение BP. Однако, из-за причин обеспечения совместимости это не
рекомендуется. Вместо этого более предпочтительным способом передачи параметров является структура, показанная в листинге 2-1. Использование этой
структуры, заимствованной большинством языков высокого уровня, поможет при разработке мобильных, многократно используемых программ. Эти программы
могут быть собраны в "инструментальный набор", который необходимо использовать во многих местах для облегчения программирования и повышения
производительности работы.
При возврате вызываемой программы параметры, которые были помещены в стек, теперь должны быть удалены. Вызывающая программа может удалить параметры
либо извлечением из стека (путем использования инструкции POP), либо просто добавлением хранимых параметров в регистр SP, например, по инструкции
add SP,N, где N представляет собой количество байтов, занимаемых параметрами. Этот способ, показанный в листинге 2-1, эффективно урезает стек в
первоначальное положение. Альтернативно ответственность за очистку стека может быть назначена вызываемой программе путем использования инструкции RET N,
где N опять количество байтов, занимаемое параметрами. При любом способе N равно количеству помещенных с помощью инструкции PUSH слов, умноженное
на 2.
Различие между этими двумя способами состоит в том, что при использовании инструкции RET N программа должна вызываться в точности с правильным
количеством параметров. Если имеется не N байтов параметров, то инструкция RET N неправильно выровняет стек и произойдет авария системы. Напротив, если
стек очищает вызывающая программа путем использования инструкции add SP,N, то каждый вызов в целевую программу может передавать различное количество
параметров.
Листинг 2-1. Передача параметров в стек
-----------------------------------------------------------------
; Вызывающая процедура
... ...
push ; пересылка последнего аргумента
... ...
push ; пересылка второго аргумента
push ; пересылка первого аргумента
call ; вызов процедуры
add sp,<2N> ; очистка стека
... ...
; Вызываемая процедура
PROC NEAR ; пример вызова процедуры near
push bp ; сохранение старого BP
mov bp,sp ; указатель ссылки на стек
... ...
mov ,[bp+4] ; доступ к первому параметру
mov ,[bp+6] ; доступ ко второму параметру
... ...
mov ,[bp+2+2N] ; доступ к последнему параметру
... ...
mov sp,bp ; восстановление SP
pop bp ; удаление сохраненного BP
ret ; возврат в вызывающую программу
ENDP
----------------------------------------------------------------
|
До тех пор, пока вызывающая программа обрабатывает стек правильно, проблем не будет. (Конечно, если вызываемая программа сможет использовать
различное количество параметров, выдаваемых от вызова к вызову).
С целью облегчения программирования следует заменить простой вызов внешними участками программы, использующими инструкции PUSH (записать в стек),
MOV (переслать), POP (извлечь из стека) и прочие. Это как раз одно из мест для вывода известных и простых макросов для выполнения этих рутинных операций.
Макросы, приведенные в листинге 2-2, помогают вызывающей программе поддерживать стек во время передачи параметров. Аналогично, макросы, приведенные в
листинге 2-3, помогают вызываемой программе при доступе и возврате параметров из стека. Все регистры, используемые в этих макросах, должны быть длиной в
слово, потому что инструкции PUSH и POP не работают с 8-битовыми регистрами.
Листинг 2-2. Макросы @CallS и @FCallS для передачи параметров стек
------------------------------------------------------------------
;; **** Макрос @PushIm: запись в стек непосредственных данных
;; через регистр BP
@PushIm MACRO arg
mov cs:mem_16,&arg
push cs:mem_16
ENDM
;; **** Макрос Вызов подпрограммы: @Calls имя,
@CallS MACRO имя_программы,список_аргументов
?count = 0
IRP argn,<&arg_list>
push &&argn ; передача параметра
?count = ?count+1
ENDM
@PushIm %?count ; передача количества параметров
call &routine_name ; вызов программы
add sp,2*(1+?count) ; очистка стека
ENDM
;; **** Макрос Вызов функции: @FCallS имя,
@FCallS MACRO имя_пр-мы,список_арг-тов,возвращаемое_значение
?count = 0
IRP argn,<&arg_list>
push &&argn ; передача параметра
?count = ?count+1
ENDM
@PushIm %?count ; передача количества параметров
call &routine_name ; вызов программы
pop &return_val ; получение возвращаемого знач-я
if ?count ; если не нуль ...
add sp,2*?count ; очистка стека
ENDIF ENDM
-----------------------------------------------------------------
|
Листинг 2-3. Макросы @Accept,@RetVal и @CRet
для приема в стек и возврата параметров из стека
-----------------------------------------------------------------
;; **** Макрос @RetVal: @RetVal регистр
@retVal MACRO возвращаемое_значение
mov [bp+4],return_val ; возврат слова
ENDM
;; **** Макрос @Accept: @Accept
@Accept MACRO список_регистров
push bp ; сохранение указателя базы
mov bp,sp ; уст-ка BP для доступа к парам.
mov &pnum,[bp+4] ; получение количества парам-ов
?count = 0
IRP reg,<®_list>
?count = ?count+1
push &® ; сохр-е рег-ра для нового знач.
mov &®,[bp+4+?count*2] ; получение параметра
ENDM
ENDM
;; **** Макрос @CRet: @CRet
@CRet MACRO список_регистров
IRP reg,<®_list>
pop &® ; восст-е сохраненного регистра
ENDM
pop bp ; восстановление указателя базы
ret ; возврат из программы
ENDM
|
Макрос @PushIm позволяет пользователям микропроцессоров 8086/8088 помещать непосредственные данные в стек. Для использования макроса сначала
необходимо определить в программном сегменте местоположение слова mem_16. Несмотря на то, что передача непосредственных данных в стек медленная и
принимаются большие коды, такой алгоритм работы создает большую свободу использования регистров.
Символ ?count в макросах @CallS и @FCallS используется для сообщения вызываемой программе количества предусмотренных параметров; для приема
количества байтов, помещенных в стек; и для использования при очистке стека после вызова. Если целевая или вызываемая программа уже знает сколько
параметров было в нее передано (обычно является случайным), то эти макросы должны быть модифицированы, чтобы обойтись без передачи и очистки
счетчика параметров. Заметим, что счетчик параметров также используется в качестве поля возврата значения для вызова функций (макросы @FCallS и
@RetVal).
Макрос @RetVal предназначен для использования с макросом @FCallS и замещает счетчик параметров, помещенный в стек с помощью макроса @FCallS,
16-битовым значением для возврата в вызывающую программу.
Макрос @Accept целевой программы работает либо с макросом @CallS, либо с макросом @FCallS для передачи параметров из стека в регистр. Этот макрос
сохраняет регистры, используемые в процессе работы. Символ ?count используется здесь для определения смещения следующего параметра в стеке. В связи с
тем, что макрос @Accept работает в направлении вверх по стеку (увеличение смещения), то этот макрос выбирает параметры из стека в порядке, обратном
тому, в котором они были помещены в стек! Заметим также, что оба макроса @Accept и @RetVal предполагают вызов процедуры near (близкий), поскольку они
допускают только 2-байтовый адрес возврата.
Последний целевой макрос @CRet восстанавливает регистры, которые были сохранены макросом @Accept. В связи с тем, что инструкции POP должны быть в
обратном порядке по отношению к инструкциям PUSH, список аргументов для макроса @CRet должен располагаться в порядке, обратном тому, какой был при
выполнении макроса @Accept. Последним действием, предпринимаемым перед инструкцией RET, является восстановление указателя базы, сохраненного макросом
@Accept.
Приведенные макросы представлены здесь скорее в качестве примеров, нежели рабочих копий и могут быть улучшены для обеспечения более полного
использования. Например, параметр инструкции PUSH (push &&argn) для обработки непосредственных данных в качестве параметров может быть замещен более
общим макросом PushOp из главы 1. Одним из ограничений текущей версии является то, что инструкция mov [bp+4],return_value в макросе @RetVal не может
возвращать переменные памяти в стек, потому что семейство микропро цессоров 8086 не поддерживает инструкцию пересылки память-память. Для распознавания
пересылки память-память и генерации передачи через непосредственный регистр этот макрос должен быть переделан.
Кроме того, необходимо иметь в виду, что макросы, представленные в листингах 2-2 и 2-3, реализуют вызывающую программу, которая несовместима ни с
одним известным языком высокого уровня. Характерно, что эти процедуры в качестве дополнительного аргумента передают количество аргументов для
вызываемой процедуры и возвращают значения для вызывающей процедуры непосредственно в стек.
MASM обеспечивает для вызываемой программы некоторые средства, упрощающие доступ к данным в стеке. Благодаря описанию structure (структура),
которая описывает данные в стеке и выравнивает указатель базы (BP) на начало структуры, к данным в стеке можно обращаться по символическим именам. Это
помогает предотвращать фатальные ошибки кодирования, которые являются результатом указания неправильного смещения. Листинг 2-4 демонстрирует директиву
MASM STRUC в этом контексте.
Листинг 2-4. Символический доступ к содержимому стека
по директиве STRUC
-----------------------------------------------------------------
; Вызывающая процедура
... ...
push ; пересылка 1-го аргумента
push ; пересылка 2-го аргумента
... ...
push ; пересылка последнего аргумента
call ; вызов процедуры
... ...
; Вызываемая процедура
StackFrame STRUC ; описание шаблона стека
dw ? ; сохраненный BP
dв ? ; адрес возврата (используйте "dw"
; для NEAR (близкий))
paramN dw ? ; последний параметр
... ...
param2 dw ? ; 2-й параметр
param1 dw ? ; 1-й параметр
StackFrame ENDS ; конец описания шаблона
;
base EQU [bp] ; база шаблона
;
PROC FAR ; пример вызова far (далеко)
push bp ; сохранение старого BP
mov bp,sp ; указатель ссылки в стеке
... ...
mov ,base.param1 ; доступ к 1-му параметру
mov ,base.param2 ; доступ ко 2-му параметру
... ...
mov ,base.paramN ; доступ к последнему пар-ру
... ...
mov sp,bp ; восстановление SP
pop bp ; сброс сохраненного BP
ret (2N) ; возврат в вызывающую программу
ENDP
----------------------------------------------------------------
|
Листинги 2-1 и 2-4 различаются по трем важным аспектам. Первое отличие заключается в порядке помещения параметров в стек. В листинге 2-1 вызывающая
программа размещает свои параметры в стеке в обратном порядке (от последнего к первому), в то время как в листинге 2-4 в прямом порядке (от первого к
последнему). Для структуры StackFrame при работе с листингом 2-1 порядок параметров должен быть изменен на противоположный (т.е. реверсирован).
(Назначение порядка "от первого к последнему" может привести к путанице в этой точке. На самом деле назначается порядок следования параметров "слева
- направо", т.е. как они появлялись бы на языке высокого уровня).
Второе отличие между примерами заключается в способе очистки переданных параметров из стека. В примере, приведенном в листинге 2-1, вызывающая
программа очищает параметры в стеке посредством инструкции add SP,<2N>. В листинге 2-4 вызываемая программа очищает стек, используя инструкцию ret
(2N).
Последнее отличие заключается в том, что в листинге 2-1 показана программа near (близкий), в то время как в листинге 2-4 показана программа far
(далекий). Если структура StackFram будет использоваться с процедурой near, то необходимо заменить директиву dd директивой dw. Это вызывает
резервирование в шаблоне для адреса возврата вызывающей программы только двух байтов, в то время как для вызываемой процедуры far требуется четыре
байта. С другой стороны, если структура будет использована в программе прерывания, то после директивы dd необходимо будет добавить дополнительную
директиву dw для резервирования памяти для флажков процессора, помещаемых в стек при прерывании.
Директива STRUC не выполняет добавление никакого кода в конечную программу. Эта директива только описывает смещения, используемые с указателем
базы BP, для облегчения задачи обращения к параметрам.
Стек также обеспечивает удобное место для хранения возвращаемых значений, но мы отложим обсуждение этой темы до тех пор, пока не обсудим различия
между функциями и подпрограммами в последующих разделах этой главы.
|
|