Главная страница «Первого сентября»Главная страница журнала «Информатика»Содержание №9/2008


В мир информатики
Эксперименты

Стек

Окончание. Начало см. “В мир информатики” № 102–104, 106, 108 (“Информатика” № 2–4, 6, 8/2008)

Пример 4. Комплексное использование стека

В примере 2 было показано, как стек работает при вызове подпрограммы. В примере 3 стек дополнительно применялся для передачи данных. Становится очевидным, насколько стек универсален: он может хранить и адреса возвратов, и данные. Более того, “с машинной точки зрения” принципиальной разницы между числом или адресом, хранящимся в стеке, вообще не существует. Убедимся в этом в ходе следующего эксперимента.

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

Обратимся к протоколу 4.

Сначала вводится подпрограмма, извлекающая в BX копию адреса возврата, а затем, как обычно, по инструкции RET, передающая управление основной программе. Последняя, зная значение адреса, вычисляет местоположение находящихся внутри нее данных и извлекает их в регистр AX. Как видно из протокола 4, данные оказываются по адресу 111, что на 7 больше, чем адрес точки выхода из подпрограммы 10a. Предлагаем читателям внимательно разобрать протокол ввода и коррекции программы самостоятельно.

Далее по директиве g = 100 · 10f запустим программу с адреса 100, обеспечивая ее останов в точке 10f. Убедимся, что в регистре AX действительно оказывается требуемое значение.

Проделанный нами пример — не просто изящное упражнение. Перед нами один из вариантов программного кода, который способен определять свое местоположение в памяти и автоматически модифицировать адрес используемых в нем данных. Иными словами, нам удалось создать простейший вариант кода, сохраняющего свою работоспособность в любом месте памяти.

Для проверки данного утверждения выполним директиву m100,113,200, которая скопирует нашу программу целиком в другую область памяти, начинающуюся с адреса 200 (проверка u200 подтвердит этот факт). Но самое интересное, что и в этом месте оперативной памяти программа по-прежнему будет правильно работать!

Задание для самостоятельной работы

Напишите подпрограмму, которая, получив управление, стирает инструкцию обращения к ней из основной программы, заменяя ее нулевыми кодами. Опробуйте случай, когда подпрограмма вызывается несколько раз из разных мест основной программы. При отладке будьте очень внимательны и осторожны!

Пример 5. Как “устроены”
операторы Writeln и PRINT?

В качестве последнего примера работы со стеком покажем, как реализовать подпрограмму, которая выводит некоторый текст. Иными словами, попробуем свои силы в реализации средствами отладчика Debug оператора вывода на экран (в языке Паскаль это Writeln, в языке Бейсик — PRINT).

Предварительно определим коды входящих в сообщение символов. Для этого мне показалось проще воспользоваться языком Бейсик, хотя читатели, вполне возможно, предпочтут другой способ. Программа, решающая такую задачу, имеет вид:

A$ = "HELLO!"
FOR I = 1 TO LEN(A$)
PRINT HEX$(ASC(MID$(A$, I, 1))); " ";
NEXT I

В ней в теле оператора цикла выделяется каждый символ величины А$, после чего определяется его десятичный код, который затем в шестнадцатеричном представлении выводится на экран. Результат выполнения программы: 48 45 4C 4C 4F 21

Перейдем к рассмотрению протокола 5.

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

Инструкция POP BX в традиционном для наших экспериментов стиле сделает регистр BX местом хранения адреса возврата. Однако в отличие от примера 4, где этот адрес просто копировался без нарушения указателя стека, здесь использован стандартный метод извлечения из стека с потерей его содержимого. Почему? В силу специфики нашей задачи! В самом деле, когда мы вызовем нашу подпрограмму, то счетчик будет установлен на следующий за вызовом адрес. Но в нашей задаче это адрес начала текста, т.е. скорее адрес данных, чем адрес возврата. Именно поэтому мы смело “берем инициативу на себя”, но тем самым “обязуемся” осуществить возврат из подпрограммы самостоятельно.

Итак, в BX — адрес выводимого текста. Теперь подготовим для цикла вывода количество символов в нем в регистре CX. Старшую часть этого регистра обнулим командой XOR CH,CH (читателям предлагается доказать, что результат логической операции “исключающее или” любой произвольной константы “с самой собой” всегда равен нулю!), а в младшую считаем первый байт, на который указывает регистр BX. Тем самым мы предполагаем, что перед текстом лежит количество байт в нем; это очень важно вспомнить при занесении кодов символов текста в память. Увеличим значение регистра-указателя BX на единицу, установив его тем самым на первую букву текста. Теперь к реализации цикла вывода на экран все подготовлено.

Далее следует короткий (из пяти машинных инструкций) цикл. В регистр AL извлекается очередной символ и значение BX тут же наращивается, чтобы подготовиться к выводу следующего. В AH заносится номер функции символа на экран и следует обращение к ней по инструкции INT 10. В результате извлеченный из памяти символ оказывается на экране. Завершается цикл командой LOOP (в переводе с английского — петля), которая вычитает из CX единицу и сравнивает результат с нулем: если осталось ноль символов, то все уже напечатано и цикл завершается, иначе — повторяется начиная с команды 108.

А теперь самое интересное. Регистр BX синхронно “скользил” вдоль текста, отслеживая его символы. Куда он указывает, когда весь текст закончился? Правильно, на инструкцию программы, которую необходимо выполнить следующей. Поэтому команда JMP BX и обеспечивает фактически завершение подпрограммы и возврат к нужному месту основной программы.

Остальная часть протокола уже не содержит ничего, что не встречалось бы в предыдущих примерах. Поэтому автор смело оставляет его читателям для самостоятельного разбора. Не забудьте только обратить внимание на переносимость реализованного нами кода, т.е. его работоспособность при копировании в другой участок памяти. Успехов!

Протокол 4

-a

1423:0100 jmp 100
1423:0102 mov bx,sp
1423:0104 mov bx,[bx]
1423:0106 ret
1423:0107 call 102
1423:010A add bx,0
1423:010D mov ax,[bx]
1423:010F int 20
1423:0111 dw 1111
1423:0113
-a100
1423:0100 jmp 107
1423:0102
-a10a
1423:010A add bx,7
1423:010D
-u
1423:0100 EB05 JMP 0107
1423:0102 89E3 MOV BX,SP
1423:0104 8B1F MOV BX,[BX]
1423:0106 C3 RET
1423:0107 E8F8FF CALL 0102
1423:010A 83C307 ADD BX,+07
1423:010D 8B07 MOV AX,[BX]
1423:010F CD20 INT 20
1423:0111 1111 ADC [BX+DI],DX
...
-g=100 10f
AX=1111 BX=0111 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1423 ES=1423 SS=1423 CS=1423 IP=010F NV UP EI PL NZ AC PE NC
1423:010F CD20 INT 20
-m100,113,200
-u200
1423:0200 EB05 JMP 0207
1423:0202 89E3 MOV BX,SP
1423:0204 8B1F MOV BX,[BX]
1423:0206 C3 RET
1423:0207 E8F8FF CALL 0202
1423:020A 83C307 ADD BX,+07
1423:020D 8B07 MOV AX,[BX]
1423:020F CD20 INT 20
1423:0211 1111 ADC [BX+DI],DX
...
-g=200 20f
AX=1111 BX=0211 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1423 ES=1423 SS=1423 CS=1423 IP=020F NV UP EI PL NZ AC PE NC
1423:020F CD20 INT 20

 

Протокол 5

-a
1423:0100 jmp 100
1423:0102 pop bx
1423:0103 xor ch,ch
1423:0105 mov cl,[bx]
1423:0107 inc bx
1423:0108 mov al,[bx]
1423:010A inc bx
1423:010B mov ah,e
1423:010D int 10
1423:010F loop 108
1423:0111 jmp bx
1423:0113 call 102
1423:0116 db 6 48 45 4c 4c 4f 21
1423:011D int 20
1423:011F
-a100
1423:0100 jmp 113
1423:0102
-u
1423:0100 EB11 JMP 0113
1423:0102 5B POP BX
1423:0103 30ED XOR CH,CH
1423:0105 8A0F MOV CL,[BX]
1423:0107 43 INC BX
1423:0108 8A07 MOV AL,[BX]
1423:010A 43 INC BX
1423:010B B40E MOV AH,0E
1423:010D CD10 INT 10
1423:010F E2F7 LOOP 0108
1423:0111 FFE3 JMP BX
1423:0113 E8ECFF CALL 0102
1423:0116 06 PUSH ES
1423:0117 48 DEC AX
1423:0118 45 INC BP
1423:0119 4C DEC SP
1423:011A 4C DEC SP
1423:011B 4F DEC DI
1423:011C 21CD AND BP,CX
1423:011E 2014 AND [SI],DL
-d
1423:0100 EB 11 5B 30 ED 8A 0F 43-8A 07 43 B4 0E CD 10 E2 ..[0...C..C.....
1423:0110 F7 FF E3 E8 EC FF 06 48-45 4C 4C 4F 21 CD 20 14 .......HELLO!. .
1423:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
...
-g
HELLO!
Программа завершилась нормально
-m100,11f,200
-u200
1423:0200 EB11 JMP 0213
1423:0202 5B POP BX
1423:0203 30ED XOR CH,CH
1423:0205 8A0F MOV CL,[BX]
1423:0207 43 INC BX
1423:0208 8A07 MOV AL,[BX]
1423:020A 43 INC BX
1423:020B B40E MOV AH,0E
1423:020D CD10 INT 10
1423:020F E2F7 LOOP 0208
1423:0211 FFE3 JMP BX
1423:0213 E8ECFF CALL 0202
1423:0216 06 PUSH ES
1423:0217 48 DEC AX
1423:0218 45 INC BP
1423:0219 4C DEC SP
1423:021A 4C DEC SP
1423:021B 4F DEC DI
1423:021C 21CD AND BP,CX
1423:021E 2014 AND [SI],DL
-g=200
HELLO!
Программа завершилась нормально

Е.. А.. Еремин,
г. Пермь

TopList