Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29209

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

Глава 6. Работа со стеком 

57 

Допустим, нам нужно временно сохранить некое число. Например, перед вызовом 

процедуры, прерывания или циклом. Вот как это будет выглядеть (листинг 6.2). 

Листинг 6.2. Временное сохранение числа в стеке 

... 
(01)    mov ax,345h 
(02)    push ax 
(03)    mov ah,10h 
(04)    int 16h 
(05)    pop ax 
... 

Здесь  мы  загружаем  в 

ax

  число  345h  (01),  сохраняем  его  (02),  ждем  нажатия 

клавиши (03), (04). Функция 

10h

 прерывания 

16h

 возвращает в 

ax

 код нажатой кла-

виши,  т. е.  затирает  в 

ax

  число,  которое  находилось  до  вызова  прерывания.  Как 

только пользователь нажал любую клавишу, выполняется строка (05), которая дос-
танет из стека число 345h и запишет его в регистр 

ax

. В итоге 

ax

 будет содержать 

число 345h, что, как говорится, и требовалось доказать. Однако стоит заметить та-
кой момент. Допустим, мы помещаем в стек содержимое следующих регистров: 

ax

bx

cx

... 
push ax 
push bx 
push cx 
... 

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

... 
pop cx 
pop bx 
pop ax 
... 

Если вы поменяете местами регистры при восстановлении, то ничего страшного 

не произойдет, только содержать они будут другие числа. Например: 

... 
mov ax,1234h 
mov bx,5678h 
 
push ax 
push bx 
pop ax 
pop bx 
... 


background image

 

Часть II. Усложняем задачи 

58 

 

Рис. 6.4. Результат неправильного использования стека 

В итоге 

ax

 будет равен 5678h, а 

bx

 — 1234h (рис. 6.4). 

Особую  осторожность  при  работе  со  стеком  следует  соблюдать  в  подпрограм-

мах (листинг 6.3). 

Листинг 6.3. Использование стека в подпрограммах 

... 
call Our_proc 
int 20h 
... 
 
Our_proc proc 
    mov ax,15 

    push ax 
    mov ah,9 
    mov dx,offset Str 
    int 21h 
    ret 
Our_proc endp 
... 

Обратите  внимание,  что  мы  "забыли"  восстановить  из  стека 

ax

  в  нашей  про- 

цедуре 

Our_proc

. В таком случае компьютер, дойдя до оператора 

ret

, вытащит из 

стека не адрес возврата, а число 15 и передаст управление на инструкцию, которая 
находится по смещению 15. Что находится по адресу 15 и дальше — не известно. 
Машина, скорее всего, "зависнет" (см. рис. 6.5 и 6.6). 


background image

Глава 6. Работа со стеком 

59 

 

Рис. 6.5. Нарушаем работу стека в подпрограмме 

 

Рис. 6.6. Результат нарушения работы стека в подпрограмме 

Следить за стеком (как уже упоминалось) позволяет пара регистров 

ss:sp

 (сег-

мент стека : смещение). Программист может менять как сегмент, так и смещение, 
но при этом следует иметь в виду, что перед сменой регистров 

ss

 и 

sp

 необходимо 

запретить  вызов  всех  аппаратных  прерываний,  а  после  изменения —  разрешить. 
Это позволяют сделать операторы 

cli

 и 

sti

 (табл. 6.3 и 6.4). 

Таблица 6.3. Оператор 

cli

 

Команда 

Перевод 

Назначение 

Процессор 

cli  

Clear interrupt 

— запретить пре-

рывания 

Запретить прерывания 

8086 


background image

 

Часть II. Усложняем задачи 

60 

Таблица 6.4. Оператор 

sti

 

Команда 

Перевод 

Назначение 

Процессор 

sti  

Restore interrupt 

— восстановить 

Разрешить прерывания 

8086 

 
Сразу  отметим,  что  запрет  вызова  аппаратных  прерываний  замертво  "вешает" 

компьютер в глазах пользователя! Восстановление же — приводит его к нормаль-
ной работе. После команды 

cli

 всегда должна идти 

sti

. Следует избегать запрета 

вызова аппаратных прерываний на длительное время. 

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

ханизму  работы  прерываний  необходимо  посвятить  целый  раздел. Если что-то не 
понятно — просто опустите, особо не заостряйте на этом внимания. 

После создания программы и ее запуска на выполнение работает не только она 

одна. Простейший пример — таймер, который автоматически вызывается пример-
но 18,2 раз в секунду для обновления часов таймера. Компьютер всегда что-то де-
лает! Даже тогда, когда, например, ждет от пользователя нажатия клавиши. 

Что происходит, когда вызывается прерывание, например, от таймера? Пример-

но то же, что и при вызове процедуры. Компьютер запоминает в стеке адрес теку-
щей команды, а также все регистры, которые изменят прерывание, и переходит на 
адрес прерывания, по которому находится процедура обработки этого прерывания 
(например,  процедура  обработки  таймера,  которая  обновит  показания  часов/ 
минут/секунд). Затем, когда процедура отработает, компьютер восстановит из сте-
ка адрес возврата и все сохраненные регистры, и наша программа  продолжит вы-
полнение, даже "не подозревая" о том, что кто-то ее прерывал в процессе работы. 
Все  это происходит очень быстро и для пользователя совсем незаметно.  Рассмот-
рим случай, когда мы меняем стековые регистры, при этом предварительно не за-
прещаем вызов аппаратных прерываний: 

... 
(01)    mov ax,100h 
(02)    mov ss,ax 
(03)    mov sp,200h 
... 

Допустим, вызов прерывания таймера пришелся на тот момент, когда наша про-

грамма  выполнила  команду  в  строке  (02). В этом случае 

ss

  равен  100h,  а  в 

sp

  еще  

не занесено число 200h. Получается так, что сегмент стека именно тот, который нам 
нужен, а смещение остается прежним, пусть это будет, допустим, 

sp=0FFFEh

. В итоге, 

ss=100h

, а 

sp=0FFFEh

. Процессор и сохранит по этому адресу данные. Какой при этом 

код программы или какие данные будут затерты — не известно. Мы ведь хотели сде-
лать 

ss=100h

, а 

sp=200h

! Хорошо, если две команды успели выполниться перед вызо-

вом  прерывания.  Хорошо  также,  если  по  адресу 

0100h:0FFFEh

  ничего  нет  (память 

свободна). А если есть? Тогда компьютер, скорее всего, опять же "зависнет". 

Отсюда вытекают три важных правила при работе со стековыми регистрами: 

 

прежде чем менять регистры 

ss:sp

, необходимо запретить вызов всех аппарат-

ных  прерываний  командой 

cli

,  а  затем,  после  смены,  разрешить  их  командой 


background image

Глава 6. Работа со стеком 

61 

sti

.  Однако  в  современных  компьютерах  (Pentium)  запрещать  прерывания  не-

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

  ss:sp

  нужно  устанавливать  на  свободную  область  памяти.  При  этом  следует 

убедиться, что код не утратил работоспособности; 

 

не  следует  забывать  о  таком  понятии,  как  переполнение  стека.  Мы  знаем,  что 
после загрузки COM-программы в память, 

ss

  равен сегменту, куда загрузилась 

программа, а 

sp = 0FFFEh

. Код программы начинается с адреса 

100h

 (

org 100h

). 

Вершина  стека —  конец  сегмента.  Если  наша  программа  занимает,  скажем, 
2000h байт,  то  можем  установить 

sp

  в  2200h.  В  этом  случае  мы  отводим  100h 

(именно  сто) байт  для  стека  (т. к.  программа  загружается  в  память  по  адресу 

100h

 (

org 100h

), то к 2000h нужно прибавить 100h). Стек, как вы помните, рас-

тет  снизу  вверх.  Если  мы  переполним  стек  (например,  поместим  более  100h 
данных), то затрется часть нашей программы снизу. Об этом следует помнить! 
В листинге 6.4 приведен полный пример корректного изменения регистров стека. 

Листинг 6.4. Корректное изменение регистров стека 

... 
cli 
mov ax,0B900h 
mov ss,ax 
mov sp,100h 
sti 
... 

Вы, вероятно, спросите: "А зачем вообще менять регистры 

ss:sp

?" 

Дело в том, что это иногда нужно делать в резидентных программах либо перед 

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

6.2. Программа для практики 

6.2.1. Оператор nop 

Прежде чем перейти к программе, рассмотрим новый оператор (табл. 6.5). 

Таблица 6.5. Оператор 

nop

 

Команда 

Перевод 

Назначение 

Процессор 

Nop 

Noperand 

— нет операнда 

Ничего не делает  8086