- •Введение
- •1. Особенности персонального компьютера
- •1.1. Оперативная память
- •1.2. Регистры
- •1.3. Представление данных
- •1.4. Представление команд
- •2. Язык ассемблера. Начальные сведения
- •2.1. Лексемы
- •2.2. Предложения
- •2.3. Директивы определения данных
- •2.4. Директивы эквивалентности и присваивания
- •2.5. Выражения
- •3. Пересылки. Арифметические команды
- •3.1. Обозначения операторов команд
- •3.2. Команды пересылки
- •3.3. Команды сложения и вычитания
- •3.4. Команды умножения и деления
- •3.5. Изменение размера числа
- •3.6. Примеры
- •3.7. Лабораторная работа № 1
- •4. Переходы. Циклы
- •4.1. Безусловный переход
- •4.2. Команды сравнения и условного перехода
- •4.3. Команды управления циклом
- •4.4. Вспомогательные операции ввода-вывода
- •4.5. Массивы
- •4.6. Лабораторная работа № 2
- •5. Программные сегменты
- •5.1. Сегментирование адресов в пк
- •5.2. Программные сегменты
- •5.3. Начальная загрузка сегментных регистров
- •5.4. Структура программы
- •6. Стек
- •6.1. Стек и сегмент стека
- •6.2. Стековые команды
- •6.3. Приемы работы со стеком
- •7. Процедуры
- •7.1. Дальние переходы
- •7.2. Подпрограммы-процедуры
- •7.3. Передача параметров через регистры
- •7.4. Передача параметров через стек
- •7.5. Локальные данные процедур
- •7.6. Лабораторная работа № 3
- •8. Ввод и вывод данных
- •8.1. Реализация основных операций ввода-вывода
- •8.2. Операции ввода-вывода
- •8.3. Пример структуры программы
- •Заключение
- •Библиографический список
- •Оглавление
- •Учебное издание
- •394026 Воронеж, Московский просп., 14
7.3. Передача параметров через регистры
Теперь следует рассмотреть проблемы, связанные с параметрами процедур.
В языках высокого уровня для того, чтобы задать фактические параметры для процедуры, достаточно лишь выписать их в операторе вызова процедуры. В языке ассемблера проблема задания параметров решается не столь просто, поэтому стоит рассмотреть ее подробно.
Попутно рассмотрим и то, как возвращается результат процедуры. Отметим, что в языке ассемблера нет формального деления на процедуры и функции, то и другое называется процедурами. Но содержательно их, конечно, можно разделить на функции и «чистые» процедуры в зависимости от того, вырабатывают они результат или нет.
Передавать фактические параметры процедуре можно по-разному. Простейший способ – передавать параметры через регистры: основная программа записывает фактические параметры в какие-то регистры, а процедура затем берет их оттуда и использует в своей работе. Аналогично можно поступить и с результатом, если он имеется: процедура записывает свой результат в какой-то регистр, а основная программа затем извлекает его оттуда. Через какие регистры передавать параметры и результат? Это личное дело автора программы, он сам определяет эти регистры.
Передача параметров по значению
Рассмотрим такой пример. Пусть надо вычислить c=max(a,b)+max(5,a-1), где все числа – знаковые и размером в слово. Вычисление mах(х,у) опишем как процедуру-функцию, при этом договоримся о следующем: первый параметр (x) основная программа должна передавать через регистр АХ, второй параметр (y) – через регистр ВХ, а результат (max) процедура должна возвращать через регистр АХ. При этих условиях процедура и соответствующий фрагмент основной программы выглядят так (для примера опишем процедуру как дальнюю):
;процедура:
АХ=mах(АХ, ВХ)
MAX PROC FAR
CMP AX, BX
JGE MAX1
MOV AX, BX
МАХ1: RET
MAX ENDP
;основная программа
...
MOV AX,А ;АХ:=а
MOV BX,B ;BX:=b
CALL MAX ;AX:=max(a,b)
MOV С, АХ ;спасти АХ
MOV AX, 5 ;АХ:=5
MOV ВХ, А
DEC ВХ ;ВХ:=а-1
CALL MAX ;АХ:=mах(5,а-1)
ADD С, АХ ;С:=mах(а,b)+mах(5,а-1)
...
Если воспользоваться терминологией языков программирования высокого уровня, то в этом примере параметры передаются по значению: перед обращением к процедуре основная процедура вычисляет значения фактических параметров и именно эти значения записывает в регистры. Теперь же рассмотрим другой способ передачи параметров – по ссылке.
Передача параметров по ссылке
Возьмем следующую процедуру на языке Паскаль:
procedure D(var x:integer); begin x:=x div 16 end;
Пусть в программе есть такие обращения к ней: D(A) и D(B), где А и В – имена переменных, значениями которых являются неотрицательные числа.
Как видно, процедура что-то присваивает своему параметру. В терминах машинного языка присваивание означает запись в какую-то ячейку памяти, а чтобы записать что-то в ячейку, надо знать адрес (имя) этой ячейки. Поэтому процедуре надо знать адрес той ячейки (А или В), в которую она должна сделать запись, и этот адрес обязана ей сообщить основная программа. Таким образом, передача параметра по ссылке означает передачу адреса (имени) ячейки, соответствующей фактическому параметру.
Как передавать адрес? Через регистр: основная программа записывает в какой-то регистр адрес фактической переменной, а процедура берет его оттуда. Какой это регистр? Вообще говоря, любой, но лучше, если это будет регистр-модификатор, т. е. ВХ, ВР, SI или DI, т. к. процедуре придется модифицировать по этому регистру.
Пусть для данной процедуры D выбран регистр ВХ. Это означает, что к началу ее выполнения в регистре ВХ будет находиться адрес той ячейки (А или В), содержимое которой она обязана изменить. В подобной ситуации добраться до этой ячейки, как уже известно (это пример на косвенную ссылку), можно с помощью конструкции [ВХ].
С учетом всего сказанного получается следующий фрагмент основной программы, соответствующий обращениям D(A) и D(B), и следующую процедуру D (на строки с командами PUSH и POP пока не обращать внимания):
;основная программа
...
LEA ВХ,А ;ВХ=адрес A
CALL D ;D(A)
LEA BX,B ;ВХ=адрес В
CALL D ;D(B)
...
;процедура: ВХ=адрес х, х:=х div 16
D PROC
PUSH СХ ;спасти СХ
MOV CL, 4
SHR WORD PTR [BX], CL ;x:=x div 16
POP СХ ;восстановить СХ
RET
D ENDP
Сохранение регистров в процедуре
Как видно, процедуре D потребовался регистр CL, в который она заносит величину сдвига. Возникает вопрос: имеет ли право процедура менять, портить этот регистр?
Это очень важная проблема. Дело в том, что в ПК не так уж и много регистров и в то же время чуть ли не в каждой команде используется тот или иной регистр. Поэтому с большой вероятностью основной программе и процедуре могут потребоваться для работы одни и те же регистры, и тем самым они будут «мешать» друг другу. Конечно, можно договориться, чтобы основная программа и процедура пользовались разными регистрами, однако сделать это очень сложно – уж очень мало регистров в ПК. Поэтому обычно поступают иначе: разрешают и основной программе, и процедуре пользоваться одними и теми же регистрами, но при этом требуют от процедуры, чтобы она сохраняла те значения регистров, которые в них записала основная программа. Достичь этого просто: в начале своей работы процедура должна спасти в стеке значения тех регистров, которые ей потребуются для работы, после чего она может использовать эти регистры как угодно, а перед выходом она должна восстановить прежние значения этих регистров, считав их из стека. Именно так мы и поступили в процедуре D. Правда, здесь есть одна тонкость: нам надо сохранить значение байтового регистра CL, но, как уже известно, в стек можно записывать только слова. Поэтому спасаем в стеке не регистр CL, а весь регистр СХ и в конце также восстанавливаем весь регистр СХ.
Такое сохранение регистров настоятельно рекомендуется делать в любой процедуре, даже если явно видно, что основная программа не пользуется теми же регистрами, что и процедура. Дело в том, что исходный текст программы в дальнейшем может измениться (а это происходит очень часто), и может оказаться так, что после этих изменений основной программе потребуются эти регистры. И хорошо, если мы при этом вспомним, что надо подправить процедуру. Чаще же всего про это забывают, и потому основная программа и процедура начинают мешать друг другу. Поэтому лучше сразу предусмотреть в процедуре сохранение регистров, тогда при любых изменениях основная программа может не волноваться за свои регистры.
Отметим, что сохранять значение регистра, через который процедура возвращает результат, конечно, не надо, поскольку в изменении этого регистра и заключается цель работы процедуры.
Передача параметров сложных типов
Теперь следует рассмотреть еще один случай передачи параметра по ссылке – когда параметром является данные сложного типа (массив, структура и т. п.). Даже если процедура не меняет это данное, ей все равно обычно передается не само данное (для него может просто не хватить регистров), а его начальный адрес. Зная этот адрес, процедура может легко добраться до любой части этого данного.
Рассмотрим следующий пример. Пусть имеются массивы из чисел без знака
X DB 100 DUP (?)
Y DB 25 DUP (?)
и требуется записать в регистр DL сумму максимальных элементов этих массивов:
DL=max(X[i]) + max(Y[i])
Поскольку здесь дважды приходится находить максимальный элемент массива, то, чтобы не повторяться, имеет смысл описать это действие в виде процедуры. Назовем ее МАХ и опишем ее при условии, что начальный адрес массива передается процедуре через регистр ВХ, количество элементов в массиве – через регистр СХ, а свой результат процедура возвращает через регистр AL. При этих соглашениях описание процедуры и фрагмент основной программы, решающий поставленную задачу, выглядят так:
;процедура MAX:AL=max(W[0..N-1]),
;где ВХ=нач.адрес W,CX=N
MAX PROC
PUSH BX ;спасти регистры,
PUSH СХ ;используемые в процедуре
MOV AL, 0 ;AL=0(нач. значение максимума)
МАХ1:CMP [BX], AL
JLE MAX2 ;W[i]>AL ==> AL:=W[i]
MOV AL, [BX]
MAX2:INC BX
LOOP MAX1
POP СХ ;восстановить регистры
POP BX
RET ;выход из процедуры
MAX ENDP
...
;фрагмент основной программы для вычисления
;DL=max(X[i])+max(Y[i])
LEA BХ, Х ;ВХ=нач.адрес массива X
MOV СХ, 100 ;СХ=число элементов в X
CALL MAX ;AL=max(X[i])
MOV DL, AL ;спасти AL
LEA BX, Y ;ВХ=нач.адрес массива Y
MOV CX, 25 ;СХ=число элементов в Y
CALL MAX ;AL=max(Y[i])
ADD DL,AL
...