Стеком называют область
программы для временного хранения произвольных данных. Разумеется, данные можно
сохранять и в сегменте данных, однако в этом случае для каждого сохраняемого
на время данного надо заводить отдельную именованную ячейку памяти, что увеличивает
размер программы и количество используемых имен. Удобство стека заключается
в том, что его область используется многократно, причем сохранение в стеке данных
и выборка их оттуда выполняется с помощью эффективных команд push и pop без
указания каких-либо имен.
Стек традиционно используется, например, для сохранения содержимого регистров,
используемых программой, перед вызовом подпрограммы, которая, в свою очередь,
будет использовать регистры процессора "в своих личных целях". Исходное
содержимое регистров извлекается из стека после возврата из подпрограммы. Другой
распространенный прием - передача подпрограмме требуемых ею параметров через
стек. Подпрограмма, зная, в каком порядке помещены в стек параметры, может забрать
их оттуда и использовать при своем выполнении.
Отличительной особенностью стека является своеобразный порядок выборки содержащихся
в нем данных: в любой момент времени в стеке доступен только верхний элемент,
т.е. элемент, загруженный в стек последним. Выгрузка из стека верхнего элемента
делает доступным следующий элемент.
Элементы стека располагаются в области памяти, отведенной под стек, начиная
со дна стека (т.е. с его максимального адреса) по последовательно уменьшающимся
адресам. Адрес верхнего, доступного элемента хранится в регистре-указателе стека
SP. Как и любая другая область памяти программы, стек должен входить в какой-то
сегмент или образовывать отдельный сегмент. В любом случае сегментный адрес
этого сегмента помещается в сегментный регистр стека
SS. Таким образом, пара
регистров SS:SP описывают адрес доступной ячейки стека: в SS хранится сегментный
адрес стека, а в SP - смещение последнего сохраненного в стеке данного (рис.
1.10, а). Обратите внимание на то, что в исходном состоянии указатель стека
SP указывает на ячейку, лежащую под дном стека и не входящую в него.
Рис. 1.10. Организация
стека:
а - исходное состояние, б - после загрузки одного элемента (в данном примере
- содержимого регистра АХ), в - после загрузки второго элемента (содержимого
регистра DS), г - после выгрузки одного элемента, д - после выгрузки двух элементов
и возврата в исходное состояние.
Загрузка в стек осуществляется специальной командой работы со стеком push (протолкнуть). Эта команда сначала уменьшает на 2 содержимое указателя стека, а затем помещает операнд по адресу в SP. Если, например, мы хотим временно сохранить в стеке содержимое регистра АХ, следует выполнить команду
push АХ
Стек переходит в состояние,
показанное на рис. 1.10, б. Видно, что указатель стека смещается на два байта
вверх (в сторону меньших адресов) и по этому адресу записывается указанный в
команде проталкивания операнд. Следующая команда загрузки в стек, например,
push DS
переведет стек в состояние,
показанное на рис. 1.10, в. В стеке будут теперь храниться два элемента, причем
доступным будет только верхний, на который указывает указатель стека
SP. Если
спустя какое-то время нам понадобилось восстановить исходное содержимое сохраненных
в стеке регистров, мы должны выполнить команды выгрузки из стека pop (вытолкнуть):
pop DS
pop AX
Состояние стека после выполнения
первой команды показано на рис. 1.10, г, а после второй - на рис. 1.10, д. Для
правильного восстановления содержимого регистров выгрузка из стека должна выполняться
в порядке, строго противоположном загрузке - сначала выгружается элемент, загруженный
последним, затем предыдущий элемент и т.д.
Совсем не обязательно при восстановлении данных помещать их туда, где они были
перед сохранением. Например, можно поместить в стек содержимое
DS, а извлечь
его оттуда в другой сегментный регистр - ES;
push DS
pop ES ; Теперь ES=DS, а стек пуст
Это распространенный прием
для перенесения содержимого одного регистра в другой, особенно, если второй
регистр - сегментный.
Обратите внимание (см. рис 1.10) на то, что после выгрузки сохраненных в стеке
данных они физически не стерлись, а остались в области стека на своих местах.
Правда, при "стандартной" работе со стеком они оказываются недоступными.
Действительно, поскольку указатель стека SP указывает под дно стека, стек считается
пустым; очередная команда push поместит новое данное на место сохраненного ранее
содержимого АХ, затерев его. Однако пока стек физически не затерт, сохраненными
и уже выбранными из него данными можно пользоваться, если помнить, в каком порядке
они расположены в стеке. Этот прием часто используется при работе с подпрограммами.
Какого размера должен быть стек? Это зависит от того, насколько интенсивно он
используется в программе. Если, например, планируется хранить в стеке массив
объемом 10 000 байт, то стек должен быть не меньше этого размера. При этом надо
иметь в виду, что в ряде случаев стек автоматически используется системой, в
частности, при выполнении команды прерывания int 21h. По этой команде сначала
процессор помещает в стек адрес возврата, а затем DOS отправляет туда же содержимое
регистров и другую информацию, относящуюся к прерванной программе. Поэтому,
даже если программа совсем не использует стек, он все же должен присутствовать
в программе и иметь размер не менее нескольких десятков слов. В нашем первом
примере мы отвели под стек 128 слов, что безусловно достаточно.