Магда Юрий
Шрифт:
При выполнении операций со стеком вся ответственность за содержимое стека ложится на программиста, поэтому нужно быть очень внимательным. Если какое-либо значение помещается в стек во время работы программы, то оно должно быть извлечено из стека перед ее завершением либо стек должен быть восстановлен каким-то другим способом. Несоблюдение этих требований приводит, как правило, к краху программы. Точно так же суммарный размер операндов, извлеченных из стека, должен быть равным размеру помещенных в него данных.
Хорошо спроектированная программа перед завершением всегда восстанавливает указатель стека к тому значению, которое было перед началом ее выполнения.
Для операций с данными в стеке не обязательно использовать команды push и pop. Вспомним, что стек представляет собой всего лишь область оперативной памяти, поэтому для доступа к данным можно применять обычные команды ассемблера, используя регистровую косвенную адресацию посредством регистра ВР (ЕВР). Для доступа к данным стека необходимо поместить содержимое указателя стека SP (ESP) в регистр ВР (ЕВР), после чего указать смещение данных. Следующий фрагмент программного кода демонстрирует такой подход (листинг 6.2).
Листинг 6.2. Доступ к данным в стеке посредством регистра ЕВР (16-разрядная версия)
Здесь содержимое переменных opl и ор2 помещается в стек, причем значение opl оказывается по адресу [SP+2], а значение ор2 – по адресу [SP] (рис. 6.6).
Рис. 6.6. Содержимое стека после размещения переменных opl и ор2
Поскольку после выполнения команды mov ВР, SP регистр ВР содержит значение SP, то значение переменной opl хранится по адресу [ВР+2], а значение ор2 – по адресу [ВР]. После выполнения последних двух команд данного фрагмента кода регистр АХ будет содержать 1149h, а регистр ВХ – 0E37L
При разработке 32-разрядных приложений для процессоров Intel Pentium использовать регистр ЕВР для доступа к данным в стеке не обязательно – можно напрямую работать с указателем стека ESP. Например, с помощью следующего фрагмента программного кода вычисляется разность операндов ор2 и opl, которая затем помещается в регистр ЕАХ:
В последних двух примерах мы не акцентировали внимание на восстановлении указателя стека, хотя в ряде случаев применение обычных команд pop может оказаться неудобным или невозможным. В таких случаях можно воспользоваться еще одним способом восстановления стека – задействовать команду add:
add ESP, n
Здесь и – количество байтов, на которое следует продвинуть указатель стека SP (ESP). Следующий пример демонстрирует восстановление указателя стека после того, как в стек были помещены три двойных слова (12 байт):
Поскольку команды push помещают в стек 12 байт (три двойных слова), то для восстановления указателя стека следует продвинуть его на это же число в сторону увеличения адресов, что и делается с помощью команды add.
Далее мы проанализируем, как используется стек при выполнении подпрограмм.
6.2. Принципы организации подпрограмм
Подпрограмма, в зависимости от выполняемых ею функций, может требовать передачи из вызывающей программы определенных данных, которые принято называть аргументами или параметрами и возвращать в вызывающую программу результат вычислений. Некоторые подпрограммы могут вообще не принимать никаких параметров и не возвращать результат. Чаще всего подпрограмма (процедура) оформляется так, как показано в следующем фрагменте кода:
Как видно из приведенного фрагмента кода, в начале процедуры (перед первой выполняемой командой) должна находиться директива proc, a после последней выполняемой команды – директива endp. Процедура обязательно должна заканчиваться командой ret. В одном ассемблерном файле с расширением ASM можно размещать несколько процедур.
Точкой входа в процедуру считается директива proc. В директиве proc после имени процедуры не ставится двоеточие, хотя имя считается меткой и указывает на первую команду процедуры. Имя процедуры можно указать в команде перехода, и тогда будет осуществлен переход на первую команду процедуры.
Директива proc может принимать один из двух параметров: near или far. Параметр near указывает на то, что процедура является ближней, a far указывает на то, что процедура дальняя. Если параметр отсутствует, то считается, что процедура имеет тип near (поэтому параметр near обычно и не указывается).
К ближней (near) процедуре можно обращаться только из того сегмента команд, где она объявлена, а к дальней (far) процедуре – из любых сегментов команд, включая тот, где она объявлена. Для 32-разрядных приложений все вызовы процедур считаются ближними.
Следует отметить, что в языке ассемблера имена и метки, описанные в процедуре, должны быть уникальными и не должны совпадать с другими именами в программе. В языке ассемблера имеется возможность создавать вложенные процедуры, то есть процедуры внутри процедур, но особых преимуществ это не дает и используется относительно редко.