Пример: Глобальная сеть INTERNET
Я ищу:
На главную  |  Добавить в избранное  

Главная/

Программирование, базы данных. /

Язык макроассемблера IBM PC

←предыдущая следующая→  
... 3 4 5 6 7 8 9 10 11 12 

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

    |              |              |--------------|

    |              |              | лок.величины |<-SP

    |              |             -2|  (m байтов)  |

    |              |              |--------------|

    |              |              0|    BP стар   |<-BP

    |адрес возврата|<-SP         +2|адрес возврата|

    | 1-й параметр |             +4| 1-й параметр |

    |  ...       |  |     ...      |

    | k-й параметр |              | k-й параметр |

    |//////////////|              |//////////////|

    |//////////////|<-BP          |//////////////|

          рис. а                        рис. б

    Первыми командами подпрограммы обычно являются следующие:

       PUSH BP      ;спасти в стеке старое значение BP

       MOV SP,BP    ;установить BP на вершину стека

       SUB SP,m     ;отвести в стеке место (m байтов) под локальные

;величины подпрограммы (состояние стека в этот ;момент показано на рис. б)

     Поясним  эти "входные" команды.  В подпрограмме  для обращения  к ячейкам стека, занятых параметрами, используется (как базовый) регистр BP: если в BP занести адрес вершины стека, то для доступа к этим ячей­кам следует использовать адресные выражения вида i[BP]  или, что то же самое, [BP+i]. (Отметим, что применять здесь регистры-модификаторы BX, SI и DI нельзя,  т.к. формируемые по ним  исполнительные адреса  будут сегментироваться по умолчанию по регистру DS,  а в данном случае нужно сегментирование по SS.)  Однако данная подпрограмма может быть вызвана из другой, также использующей регистр BP,  поэтому прежде, чем устано­вить BP на вершину стека,  надо спасти в стеке  старое значение  этого регистра,  что и делает первая из "входных" команд.  Вторая же команда устанавливает BP на вершину стека. Если предположить, что каждый пара­метр и адрес возврата занимают по слову памяти, тогда доступ к первому параметру обеспечивается адресным выражением [BP+4], ко второму -  вы­ражением [BP+6] и т.д. (см. рис. б).

    Подпрограмме  может потребоваться место  для ее локальных величин.

Такое место обычно отводится в стеке  (а для рекурсивных подпрограмм ­только в стеке)  "над" ячейкой,  занимаемой старым значением BP.  Если под эти величины нужно m байтов, то такой "захват" места  можно реали­зовать простым уменьшением значения регистра SP на m, что и делает 3-я "входная" команда.  Доступ к локальным величинам обеспечивается адрес­ными выражениями вида [BP-i]. Если подпрограмме не нужно место под ло­кальные величины, тогда третью из "входных" команд следует опустить.

Выход из подпрограммы реализуется следующими командами:

       MOV SP,BP    ;очистить стек от локальных величин

       POP BP       ;восстановить старое значение BP

       RET 2*k      ;возврат из подпрограммы и очистка стека от

                   ;параметров (считаем, что они занимают 2*k байтов) Первая из этих "выходных" команд заносит в регистр SP адрес той ячейки стека, где хранится старое значение регистра BP, т.е. происходит очис­тка стека от локальных величин  (если их не было,  то  данную  команду надо опустить).  Вторая команда восстанавливает в BP это старое значе­ние,  одновременно удаляя его из стека.  В этот момент состояние стека будет таким же, как и перед входом в подпрограмму (см. рис а).  Третья команда считывает из стека адрес возврата (в результате чего SP "опус­кается" на 2 байта),  затем добавляет к SP число,  которое должно рав­няться числу байтов, занимаемых всеми параметрами подпрограммы,  и за­тем осуществляет переход по адресу возврата.  В этот момент  состояние стека будет таким же, каким оно было перед обращением к подпрограмме.

Здесь описана универсальная схема  организации работы подпрограмм.

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

         1.7.4 Процедуры в языке ассемблера

    При составлении и вызове подпрограмм  необходимо  следить  за тем, чтобы команды CALL и RET действовали согласовано  -  были одновременно близкими или дальними. В MASM эта проблема снимается, если подпрограм­му описать как процедуру. Процедуры имеют следующий вид:

      имя_процедуры PROC [NEAR или FAR]

               ...

      имя_процедуры ENDP

    Хотя в директиве PROC после имени процедуры не ставится двоеточие, это имя относится к меткам  и его можно указывать в командах перехода, в частности в команде CALL,  когда надо вызвать процедуру.  Это же имя должно быть повторено в директиве ENDP,  заканчивающей описание проце­дуры. Предложения между этими двумя директивами образуют тело процеду­ры (подпрограмму).  Имя процедуры является фактически меткой первой из команд тела, поэтому данную команду не надо специально метить.

    Если  в директиве PROC  указан параметр NEAR или он вообще не ука­зан,  то такая процедура считается "близкой"  и обращаться к ней можно только из того сегмента команд, где она описана.  Дело в том,  что ас­семблер будет заменять все команды CALL, где указано имя данной проце­дуры, на машинные команды близкого перехода с возвратом, а все команды RET внутри процедуры  - на близкие возвраты.  Если же в директиве PROC указан параметр FAR, то это "дальняя" процедура: все обращения к ней и все команды RET внутри нее рассматриваются ассемблером как дальние пе­реходы.  Обращаться к этой процедуре  можно из любых сегментов команд. Таким образом, достаточно лишь указать тип процедуры  (близкая она или дальняя), всю же остальную работу возьмет на себя ассемблер:  переходы на нее и возвраты из нее будут автоматически согласованы с этим типом. В этом главное (и единственное) достоинство описания подпрограмм в ви­де процедур.  (Отметим,  что метки и имена, описанные в процедуре,  не локализуются в ней.)

    Например,  вычисление ax:=sign(ax)  можно описать в виде процедуры следующим образом:

    sing proc far    ;дальняя процедура

         cmp ax,0

         je  sgn1     ;ax=0 - перейти к sgn1

         mov ax,1     ;ax:=1  (флаги не изменились!)

         jg  sgn1     ;ax>0 - перейти к sgn1


      mov ax,-1   ;ax:=-1

sgn1: ret         ;дальний возврат

sign endp

      ...

Возможный пример обращения к этой процедуре:

   ;cx:=sign(var)

      mov ax,var

      call sign   ;дальний вызов

      mov cx,ax

←предыдущая следующая→  
... 3 4 5 6 7 8 9 10 11 12 


Copyright © 2005—2007 «RefStore.Ru»