Все про Assembler українською мовою на сайті net.kr.ua

 

:: Меню ::

Головна
Введення
Архітектура реального режиму
Основи програмування
Команди і алгоритми
Розширені можливості сучасних мікропроцесорів
Система команд процесорів Intel
Книга для гостей
Контакти
Добавити у вибране

:: Друзі ::

 
 

:: Лічильники ::

=

 

 

 

 

fff00e50

Використання підпрограм

Загальна ідея використання підпрограм очевидна: якщо в програмі потрібно багато разів виконувати один і той же фрагмент, його можна оформити у вигляді підпрограми і викликати в міру необхідності. Якщо підпрограма не вимагає для свого виконання ніяких параметрів і не повинна повертати в основну програму результат своєї роботи, то справа обмежується оформленням тексту підпрограми у вигляді процедури, командою 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.
Основна програма виводить на екран за допомогою функції 09h три рядки тексту. Для спрощення програми, а також щоб продемонструвати деякі прийоми програмування, виведення рядків реалізоване в циклі. Рядки зроблені однієї довжини, і модифікація зсуву до чергового рядка виконується збільшенням до вмісту регістра DX довжини рядка. Корисно звернути увагу на організацію циклу в основній програмі. У цикл, окрім команди виклику підпрограми затримки і пропозиції, що модифікує регістр DX, включена лише команда int 21h. Регістр АН з номером функції наново не настроюється. Це і не потрібно, оскільки DOS, виконуючи операцію, що зажадалася, насамперед зберігає всі регістри програми, а перед поверненням в програму їх відновлює. Тому, викликаючи функції DOS (або BIOS) можна не піклуватися про збереження регістрів - їх вміст система на руйнує. Треба тільки мати на увазі, що багато функцій DOS і BIOS після свого завершення повертають в програму деяку інформацію (число реально введених символів, доступний об'єм пам'яті, номер відеорежиму і тому подібне) Зазвичай ця інформація повертається в регістрі АХ, проте можуть використовуватися і інші регістри або їх поєднання. Тому, звертаючись в програмі до системних функцій, необхідно ознайомитися з їх описом і, зокрема, подивитися, які регістри вони можуть використовувати для повертаних значень.
Запустивши програму, можна переконатися в тому, що рядки тексту з'являються на екрані через помітні проміжки часу.
У прикладі 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 зберігає в стеку не одне, а два слова, що вплине на величину зсуву, що розраховується, щодо вершини стека.

-

:: Наша кнопка ::

Отримати код:

Підтримайте наш сайт і розмістіть нашу кнопку на своєму ресурсі.


:: Популярне ::

-


:: Посилання ::

-


 

 

 


Copyright © net.kr.ua, 2019-2025 (assem.us)