Работа процедур со стеком
Контроль пространства стека
Контроль состояния стека нужен в тех случаях, когда задача использует вложенные процедуры и уровень вложенности достаточно велик. Вложенными называются процедуры последовательно вызывающие друг друга. При этом пространство стека, доступное каждой последующей процедуре, сокращается. При неудачном стечении обстоятельств оно может быть просто исчерпано. Особенно активно используют стек рекурсивные процедуры, способные многократно вызывать самих себя, правда, в графических задачах они обычно не используются.
Если проверка свободного пространства стека предусмотрена, то она выполняется в начале процедуры. При корректной работе со стеком размер свободного пространства в байтах равен значению указателя стека (содержимому регистра sp). Для выполнения контроля надо сравнить текущее содержимое sp с той величиной, которая нужна для выполнения процедуры. Если содержимое sp больше требуемого значения, то процедура может быть выполнена, в противном случае обычно выводится аварийное сообщение и выполнение задачи прекращается.
Процедура может использовать стек для размещения промежуточных переменных, хранения содержимого регистров и для вызова вложенных процедур. Определить требуемый размер пространства в стеке можно только на основании анализа исходного текста конкретной процедуры. Это должен делать ее разработчик.
Следует отметить, что проверка доступного пространства в стеке не является обязательной. Ее можно использовать на стадии отладки задачи, а затем исключить из подпрограмм. Напоминаем, что размер стекового сегмента указывается при его описании в тексте основной программы, поэтому всегда можно выбрать оптимальное значение.
Очистка стека является обязательным действием, выполняемым перед возвратом из процедуры. На стадии отладки задачи многие ошибки могут быть связаны с некорректными действиями при очистке стека.
В общем случае подпрограмма использует три разные области стека (см. табл. В.2) и их очистка производится разными способами.
Прежде всего, очищается общедоступная часть стека. Для этого количество использованных в подпрограмме команд push и pop должно совпадать.
Следующим шагом является освобождение пространства, выделенного для хранения промежуточных переменных, разумеется, если оно выделялось. В примере В.6 показаны два варианта резервирования пространства в стеке. В зависимости от используемого варианта выбирается способ освобождения стека.
Если применялся первый вариант примера В.6, то возможны два способа освобождения зарезервированного пространства. Первый способ заключается в увеличении содержимого регистра зр на величину м, соответствующую размеру выделенного пространства в байтах (add sp, N). Второй способ состоит в том, что в регистр sp копируется содержимое bp, при условии, что оно не изменялось в процессе выполнения подпрограммы (mov sp, bp).
Если для резервирования пространства в стеке использовался второй вариант примера В.6, т. е. команда enter, то для его освобождения применяется специальная команда leave, не имеющая параметров. При ее выполнении содержимое регистра bР копируется в sp, поэтому оно не должно изменяться при выполнении подпрограммы.
После выполнения описанных действий в верхушке стека должно находиться исходное содержимое регистра bp. Оно выталкивается командой pop bp, после которой можно выполнить ret для завершения подпрограммы.
Таким образом, если процедура ориентирована на передачу параметров в стеке, то ее выполнение завершают следующие две команды:
pop bp; восстановление содержимого bpret; возврат из подпрограммы
Удаление параметров
При возврате таким способом в основную задачу или в вызывавшую процедуру в стеке остаются параметры, которые надо удалить. Это можно сделать либо при выполнении команды ret, либо в вызывающем модуле сразу после возврата из подпрограммы.
Если в качестве параметра команды ret указать число к, то после выборки адреса возврата оно будет прибавлено к указателю стека. Такая модификация команды ret введена специально для удаления параметров при возврате из подпрограммы. Число к задает размер освобождаемой области стека в байтах, оно всегда четное. Например, если при вызове процедуры в стек было записано м параметров, каждый из которых имел размер слова, то к = 2м.
Для удаления параметров в вызывавшей процедуре сразу после возврата из подпрограммы выполняется команда add sp, к, где к задает размер освобождаемой области стека в байтах, о чем мы только что говорили.
Какой из двух способов лучше? Однозначного ответа на этот вопрос не существует, на практике применяются оба варианта. Удаление параметров при выполнении команды ret проще, но оно не допустимо, если в стеке находятся выходные параметры подпрограммы, предназначенные для вызывающего модуля. Во избежание подобных случаев при вызове подпрограммы в стеке можно указывать адреса выходных параметров, а в подпрограмме записывать результаты вычислений не в стек, а по указанным адресам. По окончании выполнения подпрограммы адреса параметров не нужны, и их можно удалять командой ret.
Таким образом, вы можете выбрать любой из двух вариантов удаления параметров, но желательно остановиться на одном варианте и использовать его во всех случаях.