|
Використання підпрограм
ADO в Delphi AJAX Android C++ CakePHP CMS COM CSS Delphi Flash Flex HTML Internet Java JavaScript MySQL PHP RIA SCORM Silverlight SQL UML XML Бази даних Веб-розробка Генетичні алгоритми ГІС Гітара Дизайн Економіка Інтелектуальні СДН Колір Масаж Математика Медицина Музика Нечітка логіка ООП Патерни Подання знань Розкрутка сайту, SEO САПР Сесії в PHP Системне програмування Системний аналіз Тестологія Тестування ПЗ Фреймворки Штучний інтелект
|
Використання підпрограм
Загальна ідея використання підпрограм очевидна: якщо в програмі потрібно багато разів виконувати один і той же фрагмент, його можна оформити у вигляді підпрограми і викликати в міру необхідності. Якщо підпрограма не вимагає для свого виконання ніяких параметрів і не повинна повертати в основну програму результат своєї роботи, то справа обмежується оформленням тексту підпрограми у вигляді процедури, командою ret, що завершується, і викликом цієї процедури за допомогою команди call. Як вже наголошувалося раніше, підпрограма може і не утворювати процедуру, а бути просто частиною основної програми. Важливо тільки, щоб у неї була вхідна мітка, і щоб вона завершувалася командою ret. У наступному прикладі підпрограма delay використовується для включення в основний текст програми програмних затримок фіксованої величини. Приклад 3-8. Виклик підпрограми без параметрів
code segment
assume cs:code,ds:data
delay proc ;Процедура-подпрограмма
push CX ;Сохраним СХ основної програми
mov Cx,2000 ;Счетчик зовнішнього циклу
del1: push CX ;Сохраним його
mov Cx,0 ;Счетчик внутрішнього циклу
del2: loop del2 ;Внутренний цикл (64к кроків)
pop CX ;Восстановим зовнішній лічильник
loop del1 ;Внешний цикл (2000 кроків)
pop CX ; Відновлений СХ програми
ret ;Возврат у підпрограму
delay endp
main proc
mov Ax,data ;Настроим DS
mov Dx,ax ;на сегмент даних
mov Ah,09h ;Функция виводу на екран
mov Dx,offset npl1 ;Адрес першого рядка
mov Cx,3 ;Будем виводити рядки в циклі
cntrl1: int 21h ;Вызов DOS
cal1 delay ;Вызов підпрограми затримки
add Dx,msg_len ;Прибавим до зсуву довжину рядка
loop cntrl ;Цикл викликів DOS
mov Ax,4c00h ;Завершение програми
int 21h
main endp
code ends
data segment
msg1 db "Процес стартовал",13,10,'$'
msg_len=$-msg1
msg2 db "Процес ідет",13,10,'$'
msg3 db "Процес завершаєтся",13,10,'$'
data ends
stk segment stack
dw 128 dup(')
stk ends
end main
У тексті програми спочатку описана процедура-підпрограма, потім основна програма. Як вже наголошувалося, порядок їх опису ролі не грає; важливо тільки, щоб в завершуючій директиві закінчення трансляції end був вказаний як точка входу адреса основної програми (main в нашому прикладі).
Підпрограма реалізує затримку за допомогою вкладених циклів з командою loop, що використовує як лічильник кроків регістр СХ. У основній програмі цей регістр використовується для організації циклу виведення трьох рядків. Тому перше, що повинна зробити підпрограма - це зберегти вміст регістра СХ, для чого природно використовувати стек. Перед завершуючою командою ret регістр СХ має бути відновлений. Фрагмент, що реалізовує затримку, був описаний раніше, в розділі 3.2. Запустивши програму, можна переконатися в тому, що рядки тексту з'являються на екрані через помітні проміжки часу. У прикладі 3-8 підпрограма не вимагала параметрів. Частіше, проте, підпрограма повинна приймати один або декілька параметрів і повертати результат. В цьому випадку необхідно організувати взаємодію основної програми і підпрограми. Ніяких спеціальних засобів мови для цього не існує; передачу параметрів в підпрограму і з неї програміст організовує на свій розсуд. Для передачі параметрів як в одну, так і в інший бік можна використовувати регістри загального призначення, елементи пам'яті або стік. Наприклад, неважко перетворити підпрограму delay з прикладу 3-8 так, щоб їй можна було передавати величину необхідної затримки. Хай ця величина (у числі кроків зовнішнього циклу) передається в регістрі SI. Приклад 3-8а. Підпрограма затримки з одним параметром, передаваному в регістрі SI
delay proc ;Процедура- підпрограма
push CX ;Сохраним СХ основної програми
mov Cx,si ;Счетчик зовнішнього циклу
del1: push CX ;Сохраним його
mov Cx,0 ;Счетчик внутрішнього циклу
del2: loop del2 ;Внутренний цикл (64к кроків)
pop CX ;Восстановим зовнішній лічильник
loop del1 ;Внешний цикл (2000 кроків)
pop CX ;Восстановим СХ програми
ret ;Возврат у програму
Можна піти ще далі і скласти підпрограму так, щоб передаваний в неї параметр характеризував час затримки в секундах. Якщо не зв'язуватися з використанням системного таймера як інструмент для визначення інтервалу часу, а як і раніше реалізовувати затримку за допомогою процесорного циклу, її величина залежатиме від швидкості роботи конкретного комп'ютера і має бути підібрана експериментально. Приведений нижче варіант підпрограми правильно працював на процесорі Pentium з тактовою частотою 200 Мгц. Приклад 3-8б. Підпрограма затримки з перетворенням параметра, передаваного в регістрі SI
delay proc ;Процедура-подпрограмма
push AX ;Сохраним все
push BX ;используемые
push CX ;в програмі
push DX ;регистры
mov Ax,si ;первый співмножник в AX
mov Bx,600 ;второй експериментально
;підібраний співмножник
mul BX ;Произведение у Dx:ax
mov Cx,ax ;Нам воно потрібне в CX
del1: push CX ;Сохраним його
mov Cx,0 ;Счетчик внутрішнього циклу
del2: loop del2 ;внутренний цикл (64к кроків)
pop CX ;Восстановим зовнішній лічильник
loop del1 ;Внешний цикл ( 2000 кроків)
pop DX ;Восстановим
pop CX ;все збережені
pop BX ; на початку підпрограми
pop AX ;регистры
ret ;Возврат у програму
Експерименти показали, що для отримання правильної затримки значення параметра, що позначає число секунд, слід умножати на 600. Оскільки при множенні в системі команд МП 86 перший співмножник повинен знаходитися в регістрі АХ, а другою не може бути безпосереднім значенням і теж, отже, має бути поміщений в один з регістрів, і, до того ж, твір займає два регістри Dx:ax, доводиться зберігати при вході в підпрограму не один регістр, як в попередньому прикладі, а 4. Передаваний в SI параметр переноситься в АХ, у ВХ завантажується другий співмножник, а з отриманого за допомогою команди mul твору використовується молодша частина, що знаходиться в АХ. Таким чином, для даного варіанту підпрограми значення затримки не повинне перевищувати 109 з (109 х 600 = 65500, що майже збігається з максимально можливим значенням 65535). Слід звернути увагу на небезпеку, що підстерігає нас при виконанні операції множення. Хай значення передаваного параметра складає всього 5. При множенні на 600 вийде число 3000, яке безумовно поміщається в регістрі АХ. Проте операція множення 16-розрядних операндів
mul BX
завжди, незалежно від конкретної величини твору, поміщає його в пару регістрів Dx:ax, і, отже, при невеликій величині твору регістр DX обнулятиметься. Тому, хоча ми і не використовуємо старшу частину твору і фактично її може і не бути, збереження і подальше відновлення регістра DX є обов'язковим. Передача параметрів в підпрограму через регістри загального призначення або навіть через сегментні регістри цілком можлива, проте на практиці для передачі параметрів найчастіше використовують стек, хоч би тому, що регістрів небагато, а в стек можна помістити будь-яке число параметрів. При цьому застосовується своєрідна методика роботи із стеком не за допомогою команд push і pop, а за допомогою команд mov з непрямою адресацією через регістр ВР, який архітектурно призначений саме для адресації до стека. Перетворимо приклад 3-8а так, щоб єдиний в даному прикладі параметр (умовна величина затримки) передавався в підпрограму не через регістр SI, а через стек. Виклик підпрограми delay в цьому випадку повинен виконуватися таким чином:
push 2000 ;Проталкиваем у стек значення параметра
call delay ;Вызываем підпрограму delay
Текст підпрограми піддасться значним змінам: Приклад 3-8в. Передача параметра через стек
delay proc ;Процедура-подпрограмма
push CX ;Сохраним СХ основної програми
push BP ;Сохраним BP
mov Bp,sp ;Настроим BP на поточну вершину стека
mov CX [Bp+6] ;Скопируем із стека параметр
del1: push CX ;Сохраним його
mov Cx,0 ;Счетчик внутрішнього циклу
del2 loop del2 ;Внутренний цикл(64к кроків)
pop CX ;Восстановим зовнішній лічильник
loop del1 ;Внешний цикл
pop BP ;Восстановим BP
pop CX ;и СХ програми
ret 2 ;Возврат і зняття із стека
;непотрібного вже параметра
Команда call, передаючи управління підпрограмі, зберігає в стеку адресу повернення в основну програму. Підпрограма зберігає в стеку ще два 16-розрядні регістри. В результаті стек виявляється в змозі, зображеному на рис. 3.9. Після збереження в стеку початкового вмісту регістра ВР (у основній програмі нашого прикладу цей регістр не використовується, проте в загальному випадку це може бути і не так), в регістр ВР копіюється вміст покажчика стека, після чого у ВР опиняється зсув вершини стека. Далі командою mov в регістр СХ заноситься вміст осередку стека, на 6 байтів нижче поточної вершини. У цьому місці стека якраз знаходиться передаваний в підпрограму параметр, як це показано в лівому стовпці рис. 3.8. Конкретну величину зсуву щодо вершини стека треба для кожної підпрограми визначати індивідуально.
![]() Рис. 3.8. Полягання стека в підпрограмі після збереження регістрів. виходячи з того, скільки слів збережено нею в стеку до цього моменту. Нагадаємо, що при використанні непрямої адресації з регістром ВР як базовий, за умовчанням адресується стек, що в даному випадку і потрібний. Параметр, отриманий таким чином, використовується далі в підпрограмі точно так, як і в прикладі 3-8а. Виконавши покладене на неї завдання, підпрограма відновлює збережені раніше регістри і здійснює повернення в основну програму за допомогою команди ret, як аргумент якої указується число байтів, займаних в стеку відправленими туди перед викликом підпрограми параметрами. У нашому випадку єдиний параметр займає 2 байт. Якщо тут використовувати звичайну команду ret без аргументу, то після повернення в основну програму параметр залишиться в стеку, і його треба буде звідти витягувати (між іншим, не дуже зрозуміло, куди саме, адже всі регістри у нас можуть бути зайняті). Команда ж з аргументом, здійснивши повернення в зухвалу програму, збільшує вміст покажчика стека на значення її аргументу, тим самим здійснюючи логічне зняття параметра. Фізично цей параметр, як, втім, і решта всіх даних, поміщених в стек, залишається в стеку і буде затертий при подальших зверненнях до стека. Зрозуміло, в стек можна було помістити не один, а скільки завгодно параметрів. Тоді для їх читання треба було використовувати декілька команд mov із значеннями зсуву Вр+6, Вр+8, Bp+0ah і так далі. Розглянута методика може бути використана і при дальніх викликах підпрограм, але в цьому випадку необхідно враховувати, що дальня команда call зберігає в стеку не одне, а два слова, що вплине на величину зсуву, що розраховується, щодо вершини стека. Зверніть увагу на додаткові посиланняГоловний розділСторінки, близькі за змістомзагрузка...
|
Сторінки, близькі за змістом ![]() Асемблер (англ. assembler) — загальноприйнята назва транслятора з автокоду. Асемблер переводить початкову програму, написану на автокоді, в переміщувану програму на мові машинній. Оскільки асемблер здійснює трансляцію на мову завантажувача, при завантаженні програми необхідна налаштування умовних адрес, тобто адрес, значення яких залежать від розташування даної програми в пам'яті ЦВМ і від її зв'язків з іншими незалежно трансльованими програмами. |
|
Copyright © 2008—2026 Портал Знань.
При використанні матеріалів посилання, для інтернет-ресурсів — гіперпосилання, на Znannya.org обов'язкове.
Зв'язок
|
НТУУ "КПІ" Інженерія програмного забезпечення КПІ Лабораторія СЕТ |
|