Читайте также:
|
|
.MODEL MEDIUM;тип модели памяти
.CODE
; начало кода
PUBLIC _function_name;информация для компоновщика. Функция
;может экспортироваться
_function_name PROC FAR;название и тип функции(ближняя
;или дальняя). Ближние функции можно использовать для моделей
;памяти SMALL и COMPACT, а дальние применяются для моделей
;памяти MEDIUM, LARGE и HUGE.
push ВР;готовим фрейм стека–пролог
;функции
mov BP,SP;сохраним стек
;Работа функции
pop ВР;восстанавливаем фрейм стека
;эпилог функции
_function_name ENDP; конец процедуры
END
;конец кода
Давайте проанализируем программу, приведенную в Листинге 2.1.
§ Первая директива, которую мы встречаем — это.MODEL. Как и компилятор Си, MASM должен знать, какая из моделей памяти используется. Ключевое слово MEDIUM означает, что мы собираемся использовать модель памяти именно типа MEDIUM. Теперь я хочу напомнить вам свойства основных моделей памяти:
· Модель SMALL имеет один 64-килобайтный сегмент для кода и один сегмент для данных;
· Модель COMPACT имеет один 64-килобайтный сегмент для кода и несколько сегментов данных;
· Модель MEDIUM имеет один 64-килобайтный сегмент для данных и несколько сегментов для кода;
· Модель LARGE имеет несколько сегментов как для кода, так и для данных;
· Модель HUGE разрешает данным быть больше, чем 64К,но в остальном полностью похожа на модель LARGE.
Чаще всего мы будем использовать модели памяти MEDIUM и LARGE.
§ Следующая директива — PUBLIC. Она говорит MASM, что следующее имя будет экспортировано, то есть станет «видимо» из других модулей;
§ Теперь мы переходим к началу самой функции. В ассемблере функция начинается с директивы PROC, которая следует сразу за именем функции;
§ В этом месте мы находимся внутри исполняемой части кода. Первые две инструкции устанавливают стек таким образом, что процедура получает доступ к параметрам, передаваемым через стек. К этому мы еще не раз вернемся;
§ В конце процедуры мы очищаем стек;
§ В конце каждой процедуры ставится ключевое слово ENDP;
§ В одном блоке мы можем иметь сколько угодно процедур, но надо помнить, что самой последней должна быть директива END. Она сообщает ассемблеру об окончании программы.
Тема следующего разговора - это передача параметров. Наши ассемблерные функции не были бы так полезны, если б мы не имели возможности обмениваться с ними информацией,
Передача параметров
Языки Си и ассемблер похожи на дальних родственников, живущих в одном доме - они вынуждены придерживаться сложных взаимных условностей. Однако ассемблер значительно более примитивен. Поэтому при передаче параметров ассемблерной процедуре нам приходится сочинять множество дополнительных строк кода, обеспечивающих доступ к ним. Вначале необходимо оформить фрейм стека, как показано в Листинге 2.1. Далее необходимо получить доступ к переданным параметрам, основываясь на новом значении регистра базы (ВР). Для обеспечения доступа к параметрам вы должны четко представлять себе, как именно передаваемые параметры размещаются в стеке. К примеру, вы хотите написать процедуру, вычисляющую сумму двух чисел и возвращающую результат в регистре АХ. На языке Си, описание этой функции выглядит так:
int Add_Int(int number_1, int number_2);
При выполнении этой процедуры компилятор языка Си создаст фрейм стека и поместит туда параметры. Иными словами, значения number_1 и number_2 будут расположены в стеке. Вы можете подумать, что сначала в стек будет помещено значение number 1, а затем - number_2. Однако компилятор Си думает несколько иначе. Он помещает параметры в стек в обратном порядке, что облегчает доступ к ним. За счет применения обратного порядка размещения параметров, адрес каждого из них будет задаваться некоторым положительным смещением относительно регистра ВР, что делает жизнь намного легче. В частности, именно благодаря такому механизму, некоторые функции (например, printf) могут получать переменное число параметров. Таким образом, при вызове функции Add_Int фрейм стека будет выглядеть, как показано па рисунке 2.1 или 2.2, в зависимости от используемой модели памяти. Причина, по которой вид фрейма стека зависит от модели памяти, состоит в следующем: при вызове процедуры в стек помещается адрес команды, следующей непосредственно за командой вызова. Если мы применили модель памяти SMALL, все процедуры по определению находятся внутри одного кодового сегмента. Следовательно, для доступа из программы к любой из них нам необходимо знать только смещение. Как известно, значение смещения занимает два байта. Если же мы применяем модель памяти MEDIUM или LARGE, то должны сохранить как смещение, так и сегментную часть адреса. Вместе сегмент и смещение занимают уже целых четыре байта.
Как видно из рисунков 2.1 и 2.2, параметры помещаются в стек в том порядке, который обеспечивает их адресацию положительными смещениями относительно значения регистра базы (ВР). Следовательно, для доступа к параметру number 1 вы должны использовать [ВР+4] или [ВР+6], в зависимости от установленной модели памяти. В качестве примера рассмотрим полный текст функции Add_Int. Она вычисляет сумму двух передаваемых в качестве аргументов чисел. Результат возвращается в регистре АХ, который, в соответствии с соглашениями языка Си, используется для возврата 16-битных значений.
Дата добавления: 2015-07-12; просмотров: 84 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
ОСНОВЫ ЯЗЫКА АССЕМБЛЕРА | | | Листинг 2.2. Простая процедура сложения. |