Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
л.р.1-12.СПРГ-1(40, укр).doc
Скачиваний:
5
Добавлен:
27.08.2019
Размер:
2.33 Mб
Скачать

3.2. Параметри процедур і значення, що повертаються

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

Слід зазначити, що маються і певні обмеження на використання цього методу. Справа в тім, що процесор має не так багато регістрів, у той час як чи ледве не в кожній команді потрібно той або і регістр. Існує висока імовірність того, що зухвалій програмі і процедурі одночасно будуть потрібні для роботи ті самі регістри, що ускладнить ситуацію. Можна спробувати розробити додаток таким чином, щоб зухвала програма і процедура використовували різні регістри, хоча зробити це досить складно — знов-таки через їхній обмежену кількість.

Найпростіший варіант уникнути подібної ситуації — зберегти регістри в стеці при вході в підпрограму і відновити них при виході з підпрограми. Які ж регістри процесора можна використовувати для передачі параметрів і повернення результату? Програміст може вибирати ті або і регістри, виходячи зі своїх розумінь, але і тут є деякого правила.

Найчастіше для передачі параметрів застосовують регістри ЕАХ, ЕВХ, ЕСХ, EDX, небагато рідше — EBP, ESI, EDI. Регістр ЕВР звичайно використовується разом з регістром покажчика стека ESP для доступу до параметрів, що знаходиться в стеці, і про це ми поговоримо окремо. Регістри ESI і EDI зручні при виконанні операцій з масивами даних у якості індексних, хоча можна застосовувати них за своїм розсудом.

Приклад. Для простоти будемо вважати наш програмний код 32-розрядним і використовувати регістри і перемінні тієї ж розрядності. Припустимо, що потрібно знайти менше з двох цілих чисел і обчислити абсолютну величину (модуль) цього мінімуму. Для рішення цієї задачі розробимо двох процедур на асемблері: minint — для обчислення мінімуму і minabs — для визначення його абсолютного значення.

Процедура minint як параметри приймає два цілочисельних значення, процедура minabs як єдиний параметр — одне цілочисельне значення.

Умовимося перший параметр процедури minint передавати в регістрі ЕАХ, а другий — у регістрі ЕВХ. Припустимо, що в нас мається два цілих числа, розташовані в перемінних і1 і і2. Крім того, створимо перемінні min_val і abs_val для збереження мінімуму і модуля числа відповідно. Обидві процедури повертають результат у регістрі ЕАХ. Результат виконання процедури minint є вхідним параметром для процедури minabs.

...

i1 DD 34

i2 DD 17

min_val DD?

abs_val DD?

...

mov EAX, і1

mov ЕВХ, і2

call minint

; мінімум двох чисел і1 і і2 знаходиться в регістрі EAX. Збережемо

; значення в перемінної min_val і знайдемо абсолютне значення

mov min_val, EAX

call minabs

; збережемо модуль числа в перемінної abs_val

mov abs_val, EAX

...

; тут з'являються процедури minint і minabs

minint:

cmp ЕАХ, ЕВХ

jl exit

mov ЕАХ, ЕВХ

exit:

ret

minabs:

mov EAX, min_val

cmp EAX, 0

jge quit

neg EAX

quit:

ret

При створенні цього програмного коду ми не робили ніяких припущень щодо схоронності вмісту регістрів. Якщо зухвала програма в момент передачі управління процедурі minint використовувала регістр ЕВХ, то його вміст може бути зруйновано викликуваною процедурою. Щоб уникнути цього, потрібно зберегти регістр ЕВХ у стеці:

mov EAX, i1

push EBX

mov EBX, i2

call minint

pop EBX

Збереження регістрів бажано робити в будь-якій процедурі, навіть якщо очевидно, що основна програма не використовує ті ж регістри, що і процедура. Програмний код надалі може змінитися (що відбувається дуже часто), і може виявитися так, що після цих змін основній програмі будуть потрібні ці регістри. Найкраще передбачити збереження всіх регістрів, скориставшись спеціальними командами pusha, pushad, popa і popad.

Помітимо, що зберігати значення регістра, за допомогою якого процедура повертає результат (звичайно це АХ або ЕАХ), не потрібно, оскільки в зміні цього регістра і полягає мета роботи процедури.

Передача параметрів через регістри — зручний метод і використовується дуже часто. Він є ефективним, коли число параметрів невелике; якщо ж параметрів багато, те для них просто не вистачить регістрів. У такому випадку реалізують інший спосіб передачі параметрів — через стек: основна програма записує фактичні параметри (їхні значення або адреси) у стек, а процедура потім їх відтіля витягає. Це найбільш розповсюджений спосіб, застосовуваний у більшості програм.

Як процедура одержує доступ до параметрів? Загальноприйнятим для цих цілей вважається регістр ЕВР. У нього необхідно помістити адресу вершини стека (на нього вказує регістр ESP), а потім використовувати виразу виду [ЕВР+n] для доступу до параметрів процедури. Максимальне значення числа і визначається кількістю параметрів і повинне бути кратним 2 (2, 4, 6, 8 і т. д.). При цьому бажано зберегти регістр ЕВР, оскільки він може знадобитися в основній програмі.

Розглянемо приклад процедури (sub2), у якій потрібно знайти різниця двох цілих чисел, причому параметри цій процедурі передаються через стек. Як звичайно, процедура повертає результат операції в регістрі ЕАХ. Покладемо, що зухвала програма передає процедурі як параметри цілочисельні перемінні і1 і і2, а результатом виконання процедури буде різниця і1 — і2.

Перше, що повиннао зробити зухвала програма, — помістити передані параметри в стек. Потім процедура повинна витягти них зі стека й обробити. От вихідний текст програмного коду:

i1 DD 34

12 DD 190

push i2 i1

call sub2

pop i1 i2

sub2:

push EBP

mov EBP, ESP

mov EAX. Dword [EBP+8]

sub EAX. dword [EBP+12]

pop EBP

ret

Проаналізуємо вихідний текст. У програмному сегменті за допомогою наступних команд параметри il і і2 містяться в стек:

push і1 push і2

call sub2

Потім викликається процедура sub2. Уміст стека після виконання цих команд буде таким, як показано на мал. 8.

Оскільки програма оперує подвійними словами, то вміст покажчика стека після виконання команд push зміщається на 8. Чергова команда call sub2 поміщає в стек адреса команди, що буде виконуватися наступної. Після входу в процедуру sub2 за допомогою команди push EBP у стеці зберігається вміст регістра ЕВР, що повинний використовуватися для доступу до параметрів і1 і і2 у стеці. Область стека буде виглядати так, як показано на мал. 9.

Тепер, наприклад, щоб звернутися до перемінного і1, випливає в якості одного з операндів указати [ЕВР+8], а перемінна і2 буде знаходитися за адресою [ЕВР+12]. Як бачимо, перша поміщена в стек перемінна має найбільша адреса, а остання — найменший. Наступні дві інструкції асемблера виконують вирахування і2 з il, тобто і1 - і2:

mov EAX, dword [EBP+8]

sub EAX, dword [EBP+12]

Результат вирахування залишається в регістрі EAX і повертається зухвалій програмі. Передостання команда pop EBP відновлює вміст регістра ЕВР, а команда ret передає управління інструкції, що випливає за командою call, шляхом витягу вмісту стека адреси цієї інструкції і приміщення цієї адреси в регістр EIP.

Таким чином, після завершення процедури sub2 у стеці залишаються перемінні і1 і і2. Зухвала програма може зберігати визначені дані в стеці по визначених зсувах щодо вершини стека, але проблема в тім, що покажчик стека не відповідає тому, що потрібний програмі. Якщо відбувається звертання до стеку зухвалої програми, то негайно настає крах. Тому дуже важливо відновити або, по-іншому, очистити стек до того стану, яке він мав перед викликом процедури. Очистити стек може як зухвала програма, так і процедура.

Найпростіший варіант — після виклику sub2 виконати дві команди pop у зворотному порядку, що і зроблено в цьому фрагменті коду:

pop і1 і2

Ці команди ніякої корисної функції, крім відновлення покажчика стека, не виконують. Зухвала програма може очистити стек і більш витонченим способом, відразу ж після команди call використовуючи команду

add ESP, 8

У результаті виконання цієї команди покажчик стека ESP зміститься на два подвійних слова, що нам і потрібно. Тоді послідовність команд виклику процедури і відновлення стека буде мати вигляд

push i2 i1

call sub2

add ESP, 8

Процедура sub2 може і сама відновити стек при виході. Це можна зробити за допомогою спеціальної команди ret n, де п — кількість байтова, що підлягають видаленню зі стека. Програмний код процедури sub2 у цьому випадку зміниться:

sub2:

push EBP

mov EBP, ESP

mov EAX, dword [EBP+8]

sub EAX, dword [EBP+12]

pop EBP

ret 8

Команда ret — це одна з модифікацій команди ret п при і = 0. При завданні параметра п потрібно пам'ятати, що в операнді не повинний враховуватися адреса повернення — команда ret зчитує його до очищення стека.

З погляду ефективності програми краще, якщо очищення стека буде робити сама процедура. Якщо звертань до процедури багато, то в основній програмі команду add прийдеться використовувати багаторазово, у той час як викликувана процедура одна й у ній можна викликати команду ret n. Це корисне правило для оптимізації програм: якщо якась дія може бути виконано або в основній програмі, або в процедурі, те краще, якщо це буде зроблено в процедурі. У цьому випадку потрібно менше команд.

Така загальна схема передачі параметрів через стек. Ще раз нагадаю, що цей спосіб передачі параметрів універсальний, його можна використовувати при будь-якому числі параметрів. Однак цей спосіб складніше, ніж передача параметрів через регістри, тому бажано передавати параметри через регістри, так простіше і коротше. Що ж стосується результату процедури, то він украй рідко передається через стек і звичайно передається через регістр. Загальноприйнято, що результат виконання процедури повертається в регістрі ЕАХ.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]