Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
2014- СП 3.0 ЛАБЫ ОБЯЗАТ.doc
Скачиваний:
99
Добавлен:
01.03.2016
Размер:
896 Кб
Скачать
      1. Передача параметров в процедуру

Процедуры (подпрограммы) – это мощный механизм логического деления большой программы на функционально законченные модули.

При этом идеальную программу можно было бы представить в виде основной программы, которая «делает всю работу» только вызовами своих подпрограмм. Именно в этом смысле «программирование с подпрограммами» в теории программирования получило когда-то «технологии модульного программирования».

Но в «чуть более, чем в 100% случаев» процедура, в которой не предусмотрена параметризация выполнения, мало чего стоит. Ну, подумайте сами: предположим, в программе нужно рассчитывать стоимость перевозки 1 тонны груза на разные расстояния. Так неужели для расчета стоимости перевозки от Чернигова до Киева нужно иметь одну процедуру, а для расчета стоимости перевозки от Киева до Одессы – другую?! Весь алгоритм расчета одинаков, только расстояние различно. Даже школьник вам скажет – нужна одна процедура, но с параметром-расстоянием.

Вот поэтому разберёмся, как в процедуры передаются параметры и как в процедуре происходит работа с ними.

Самый простой способ передать процедуре одиночное число-параметр – это перед вызовом процедуры записать его в любой свободный регистр общего назначения, затем вызвать процедуру, а она, в сою очередь, прочитает его оттуда и использует запрограммированным образом.

Способ прост, но регистров общего назначения не так много и считать «свободным» какой-то – это значит какое-то время не записывать в него ничего, что перетрёт занесенный в него параметр. Неудобно. Поэтому применимость такого способа ограниченная.

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

Как процедура будет находить параметры в стеке?

Очень кстати здесь то, что мы (см. прошлый подраздел) запоминаем в регистре EBPположение «базы», то есть определенной точки в стеке, которая остается неизменной всё время, пока выполняется процедура. Пока мы считаем этой базовой точкой адрес адреса возврата (дальше это немного скорректируется, потому что кое-что пока остается неучтенным).

Рассмотрим, как расположатся параметры в стеке относительно базы, если мы перед вызовом занесем в стек, предположим, пару параметров:

; основная программа.

. . .

К1

К2

К3

К4

Push par1

Push par2

Call P55

K5

. . .

В самой процедуре пока ничего не меняем, то есть она первое, что делает – это

Mov EBP, ESP ; пролог - запоминаем адрес адреса возврата

Получившуюся ситуацию в стеке изобразим рисунком 7.4.

Рисунок 7.4 – Состояние стека сразу после выполнения Mov EBP, ESP

Чтобы процедура могла обращаться к параметрам (читать их или перезаписывать), нужно использовать факт известного смещения параметров относительно базы: параметр 2 смещен относительно базы на 4 байта (044 – 040 = 4), а параметр 1 – на 8 байт (048-040=8).

Значит, символическим именем параметра 1 внутри процедуры будет [EBP + 8]. Квадратные скобки означают «взять по адресу». То есть это словами можно назвать «содержимое ячейки стека, расположенной на 8 байт вперёд от адреса, содержащегося в EBP». Соответственно, для параметра 2 - [EBP + 4].

ПРАВИЛЬНОЕ ПОЛОЖЕНИЕ БАЗЫ В СТЕКЕ

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

Предположим, основная программа вызвала процедуру. Процедура переприсвоила ЕВР (в нашем примере это 142), проработала, сколько ей положено, успешно завершилась. Управление возвращено основной программе. Основная программа «хочет» продолжить обрабатывать массив, используя ЕВР, как она и делала до вызова процедуры. Делается попытка прочесть очередной элемент массива по адресу [EBP]… Что будет прочитано? Что-то, находящееся по адресу 142. А это требуемый элемент массива? Гарантируем, что нет. Результат – неправильное выполнение программы.

Где мы ошиблись?

Ответ очевиден. Перед переприсвоением нового значения регистру ЕВР следовало «старое» значение сберечь. Потом, перед возвратом в основную программу, опять его восстановить. Это и будет правильная работа с данными по принципу (как в медицине) «главное – не навреди».

Технически эти действия выражаются в том, что, во-первых, к прологу процедуры добавляется оператор сохранения старого ЕВР, и пролог будет выглядеть так:

; Фрагмент 7.1 - окончательный вид пролога процедуры

Push EBP

Mov EBP, ESP ; запоминаем «точку базы» в ЕВР

Во-вторых, для восстановления старого значения ЕВР перед возвратом в основную процедуру в эпилог процедуры добавляется один оператор извлечения сохраненного в стеке значения в ЕВР:

Mov ESP, EBP ; возвратить указатель стека к базе

Pop EBP ; восстановить старое ЕВР. Указатель стека автоматом

; становится на адрес возврата. Всё готово к RET.

На рис.7.5 показано окончательное состояние стека после вызова процедуры и выполнения в ней пролога согласно фрагменту 7.1. Показанное там положение базы является окончательно правильным, и используется в реальном программировании.

Рисунок 7.5 – Окончательный вид стекового кадра вызванной процедуры Р55.

Обратим внимание также на то, что параметры теперь расположились в стеке на 4 байта дальше базы, чем мы видели на рис. 7.4. Значит, символическим именем параметра 1 внутри процедуры будет теперь не [EBP + 8], а [EBP + 12]. Соответственно, для параметра 2 - [EBP + 8].

ОЧИСТКА СТЕКА ОТ ПАРАМЕТРОВ

При возврате из процедуры командой RET(см. рис. 7.5) указатель стека остановится на значении 044. ВедьRETпо смыслу эквивалентна “POPEIP”. То есть ячейкиpar1 иpar2 так и остаются в стеке. Такая ситуация недопустима, потому что при повторных вызовах процедуры стек будет забиваться неудаленными параметрами.

Для решения этой проблемы придумана специальная форма команды RET– это «RETчисло». «Число» прибавляется кESPпри возврате из процедуры, освобождая тем самым соответствующее число байт стека.

В нашем случае достаточно написать в программе не RET, аRET 8, и при возврате из процедуры значениеESPбудет не 044, а 052. То есть стек будет точно в том состоянии, в каком он был перед началом передачи параметров в стек. Никакого засорения стека.