Стек
Стек
Стек - організована спеціальним чином ділянка пам'яті, яка використовується для тимчасового зберігання змінних, передачі параметрів для підпрограм, які викликаються, і збереження адреси повернення при виклику процедур та переривань. Найлегше уявити стек у вигляді стосу аркушів паперу (це одне із значень слова stack в англійській мові) - ви можете класти і забирати аркуші лише з вершини цього стосу. Інакше стек називають структурою LIFO (Last In First Out). Тому, якщо записати в стек числа 1,2,3, то при зчитуванні вони будуть у зворотньому порядку - 3,2,1. Стек розташовується в сегменті пам'яті, який описується SS, і поточне зміщення вершини стека відображене в регістрі ESP(вказівник стеку), причому під час запису значення цього зміщення зменшується, тобто він "росте вниз" від максимально можливої адреси. Таке розміщення стека "вверх ногами" може бути необхідним, наприклад, в безсегментній моделі пам'яті, коли всі сегменти, включаючи сегменти стеку та коду, займають одну й ту ж область - всю пам'ять цілком. Тоді програма виконується в нижній області пам'яті, в області малих адрес, і EIP(вказівник інструкції - 16-бітна форма IP) росте, а стек розташовується в верхній області пам'яті, ESP зменшується.
При виклику підпрограми параметри в більшості випадків поміщаються в стек, а в EBP(вказівник бази) записують поточне значення ESP. Якщо підпрограма використовує стек для зберігання локальних змінних, ESP зміниться, але EBP можна буде використовувати для того, щоб зчитувати значення параметрів напряму зі стеку (їх зміщення запишуться як EBP + № параметру).
Типовий приклад програми, що працює зі стеком
Команди для роботи зі стеком
Для роботи зі стеком є дві основні команди PUSH i POP.
PUSH джерело
Поміщає вміст джерела в стек. Джерело може бути регістр, сегментний регістр, безпосередній операнд або змінна. Фактично ця команда зменшує ESP на розмір джерела в байтах (2 або 4) і копіює вміст джерела в пам'ять за адресою SS:[ESP]. Команда PUSH майже завжди використовується в парі з POP (зчитати дані зі стеку). Тому, щоб скопіювати вміст одного сегментного регістру в інший (що не можна виконати однією командою mov), можна використати таку послідовність команд:
push cs
pop ds ;тепер ds вказує на той же сегмент, що і cs.
Інший варіант застосування PUSH/POP - тимчасове зберігання змінних, наприклад,
push eax ;зберігає поточне значення eax.
... ;тут розташовуються які-небудь команди, які використовують eax.
pop eax ;відновлення старого значення eax.
Починаючи з процесора 80286 команда PUSH ESP (або SP) поміщає в стек значення ESP до того, як вона його зменшить, а на 8086 регістр SP розташовувся в стеку вже зменшеним на 2.
POP прийомник
Поміщає в прийомник слово або подвійне слово, яке знаходиться на вершині стеку, збільшуючи ESP на 2 або 4 відповідно. РОР виконує дію, яка повністю протилежна PUSH. Прийомником може бути регістр загального призначення, сегментний регістр крім CS (щоб завантажити CS зі стеку, слід скористатися командою RET), або змінна. Якщо в ролі прийомника виступає операнд, який використовує ESP для непрямої адресації, команда РОР вираховує адресу операнда уже після того, як вона збільшила ESP.
Приклад програми, що ілюструє роботу команд PUSH i POP
Передача параметрів через стек
Можна розділити на (C, Pascal, Fortran, BASIC) і непроцедурні (LISP, FORTH, PROLOG), де процедури - блоки коду програм, які мають одну точку входу і одну точку виходу і які повертають керування на наступну команду після команди передачі керування процедурі. однаково легко можна використовувати як процедурну мову і як непроцедурну.
Процедури можуть отримувати або не отримувати параметри із процедури, яка їх викликає, і можуть повертати або не повертати результат (процедури, які щось повертають, називаються функціями в Паскалі, але Асемблер не робить якоїсь різниці між ними). Параметри можна передавати за допомогою одного з шести механізмів:
за значенням;
за посиланням;
за значенням, яке повертається;
за результатом;
за ім'ям;
за відкладеним обчисленням.
Параметри можна передавати в одному з п'яти місць:
в регістрах;
в глобальних змінних;
в стеку;
в потоці коду;
в блоці параметрів;
Відповідно всього в асемблері можливі 30 різних способів передачі параметрів для процедур. Розглянемо передачу параметрів через стек.
Параметри поміщаються в стек зразу перед викликом процедури. Саме цей метод використовують мови високого рівня, такі як Сі і Паскаль. Для зчитання параметрів зі стеку зазвичай застосовують не команду РОР, а регістр BР, в який поміщають адресу вершини стеку після входу в процедуру.
push parametr1 ;помістити параметр в стек
push parametr2
call procedure
add sp,4 ;звільнити стек від параметрів
[...]
procedure proc near
push bp
mov bp,sp
(команди, що можуть використовувати стек)
mov ax,[bp+4] ;зчитати параметр 2
;його адреса в сегменті стека bp+4, тому що при виконанні
;команди call в стек помістили адресу повернення - 2 байти
;для процедури типу near (або 4 для far), а потім ще і ВР - 2 байти
mov bx,[bp+6] ;зчитати параметр 1
(решта команд)
pop bp
ret
procedure endp
Параметри в стеку, адреса повернення і старе значення ВР разом називається активізаційним записом функції.