→ Пошук по сайту       Увійти / Зареєструватися
Знання Асемблер Команди і алгоритми

Цикли і умовні переходи

Цикли

Цикли, програми, що дозволяють виконати деяку ділянку, багато разів, в будь-якій мові є одній з найбільш споживаних конструкцій. У системі команд МП 86 циклів реалізуються, головним чином, за допомогою команди loop (петливши), хоча є і інші способи організації циклів. У всіх випадках число кроків в циклі визначається вмістом регістра СХ, тому максимальне число кроків складає 64 До.

Розглянемо простий приклад організації циклу. Хай в програмі зарезервовано місце для масиву розміром 10000 слів, і цей масив треба заповнити натуральним рядом чисел від 0 до 9999. Ці числа, що заповнюють послідовні елементи масиву, іноді називають числами-заповнювачами. Відповідний фрагмент програми виглядатиме таким чином:

;У сегменті даних

array dw                     10000 dup(0)

;У програмному сегменті

mov Bx,offset array  ; Адреса масиву

mov Si,0                     ;Индекс

mov Ax,0                   ; Початкове значення заповнювача

mov Cx,10000          ; Лічильник циклу

fill: mov [BX] [SI],ax  ;Заполнитель пошлемо в масив

inc AX                         ;Инкремент заповнювача

add Si,2                      ; модифікація індексу

loop fill                         ; Команда циклу 

На етапі підготовки ми заносимо в регістр ВХ відносну адресу початку масиву, що ототожнюється з його ім'ям array, встановлюємо початкове значення індексу елементу масиву в регістрі SI (з таким же успіхом можна бьшо узяти DI) і початкове значення числа-заповнювача. Сам цикл складається з трьох команд - єдиної змістовної команди засилання числа-заповнювача в черговий елемент масиву (за адресою, яка обчислюється, як сума вмісту регістрів ВХ і SI), а також модифікації числа-заповнювача і індексу чергового елементу масиву. Завершуючою командою loop управління передається на мітку fill, і цикл повторюється стільки раз, яке вміст СХ, в даному випадку 10000 кроків.

Слід звернути увагу на команду модифікації індексу - в кожному кроці до вмісту SI додається 2, оскільки масив складається з двобайтових слів. Якби потрібно було заповнити байтовий масив, то в кожному кроці вміст регістра циклу SI слід було збільшувати на 1.

Варто відзначити деякі деталі, пов'язані з механізмом виконання команди loop. При реалізації цієї команди процесор спочатку зменшує вміст регістра СХ на 1, а потім порівнює отримане число з нулем. Якщо СХ > 0, перехід на вказану мітку виконується. Якщо СХ = 0, цикл розривається і процесор переходить на команду, наступну за командою loop. Тому після нормального виходу з циклу вміст СХ завжди дорівнює 0.

Інша обставина пов'язана з кодуванням команди loop. У її коді під зсув до точки переходу відводиться всього 1 байт. Оскільки зсув має бути величиною із знаком, максимальна відстань, на яку можна передати управління командою loop, складає від -128 до +127 байт (хоча досить важко уявити собі цикл, в якому перехід здійснюється вперед). Іншими словами, тіло циклу обмежується всього 128 байтами. Якщо циклічно повторюваний фрагмент програми має велику довжину, цикл доведеться організувати іншим, складнішим способом:

;Організація довгого циклу

mov Cx,10000      ;Счетчик циклу

fill:                     ; Влучна початки циклу

...                       ; Тіло довгого циклу

dec CX                 ; Декремент лічильника циклу

cmp Cx,0             ; Відпрацьовано задане число кроків?

je finish               ; Так, на мітку продовження програми

jmp fill                 ; Ні, на початок циклу

finish:                  ; Продовження програми 

 

У цьому, вельми типовому фрагменті ми "уручну" зменшуємо вміст лічильника циклу і порівнюємо набутого значення з 0. Якщо СХ = Про, це означає, що в циклі виконано задане число кроків, і командою умовного переходу je здійснюється перехід на продовження програми (влучна finish). Якщо СХ ще не дорівнює нулю, командою безумовного переходу jmp здійснюється повернення в початок циклу. Як було показано в гл. 2, команда jmp дозволяє перейти в будь-яку точку сегменту, і обмеження на розмір тіла циклу знімається.

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

mov Cx,2000      ;Счетчик зовнішнього циклу

outer: push CX    ; Збережемо його в стеку

mov Cx,0            ;Счетчик внутрішнього циклу

inner: loop inner  ; loop внутрішнього циклу

pop CX                ;Восстановим зовнішній лічильник

loop outher          ; loop зовнішнього циклу 

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

У приведеному вище фрагменті зовнішній цикл виконується 2000 разів; внутрішній - 65536 разів. За рахунку числа кроків внутрішнього циклу використовується явище обертання, яке вже згадувалося раніше. Початкове значення в регістрі  СХ  дорівнює нулю; після виконання тіла циклу 1 раз команда loop зменшує вміст СХ на 1, що дає число Ffffh (яке можна розглядати, як -1). В результаті цикл повторюється ще 65535 разів, а в сумі - точно 64 До кроків. 

Команда loop внутрішнього циклу передає управління на саму себе, тобто тіло внутрішнього циклу складається з єдиної команди loop. У цьому немає нічого незаконного. Будь-яка команда, у тому числі і loop, вимагає якогось часу для свого виконання, і повторення 64 До раз команди loop дає деяку тимчасову затримку (на сучасних процесорах порядку тисячної частки секунди).

Перейдемо тепер до розгляду команд умовних переходів.

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

cmp Ax,bx          ;Сравнение двох регістрів

je equal                 ;Переход, якщо Ax=bx

cmp Si,mem        ;Сравнение регістра і елементу пам'яті

jne notequ            ;Переход, якщо Si<>mem

int 21h                  ;Вызов DOS

jc syserr               ;Переход, якщо була помилка

                             ;і прапор Cf=1

or Bx,bx            ;Анализ BX

jz zero                 ;Переход, якщо Bx=0

inpt: in  Al,dx   ;Ввод даного з пристрою

test Al,80h        ;Анализ бита 7 в даному

je inpt                  ;Ввод до тих пір, поки

                            ;битий 7=0 (очікування установки бита 7)

test Ax,7            ;Анализ бітів 0,1,2 в AX

jne found             ;Переход, якщо хоч би 1 битий

                             ;з них встановлений

test Di,ofh         ;Анализ бітів 0...3 в DI

jz reset                ;Переход, якщо всі вони скинуті 

У гл. 2 наголошувалося, що двійкові числа, записувані в регістри процесора або елементу пам'яті, можна розглядати, або як числа істотно позитивні, тобто числа без знаку, або як числа із знаком. Наприклад, адреси осередків, зрозуміло, не можуть бути негативними. Тому число Ffffh, якщо по сенсу програми воно є адресою, позначає 65535. Якщо, проте, то ж число Ffffh вийшло в арифметичній операції віднімання 2 з 1, то його треба розглядати, як - 1. Так само поняття знаку безглузде по відношенню до кодів символів, які з рівним успіхом можуть набувати будь-якого значення з діапазону 0...255. З іншого боку, ми можемо умовно вважати, що коди символів першої половини таблиці   ASCII   позитивні, а коди другої половини таблиці (у них встановлений старший біт) негативні, і використовувати для обробки символів команди, чутливі до знаку.

У складі команд умовних переходів є дві групи команд для порівняння чисел без знаку (це команди ja, jae, jb, jbc, jna, jnae, jnb і jnbe) і чисел із знаком (jg, jge, jl, jle, jng, jnge, jnl і jnle). У абревіатурах цих команд для порівняння чисел без знаку використовуються слова above (вище) і below (нижче), а для чисел із знаком - слова greater (більше) і less (менше).

Різниця між тими і іншими командами умовних переходів полягає в тому, що команди для чисел із знаком розглядають поняття "Больше- менше" стосовно числової осі -32К...0...+32К, а команди для чисел без знаку - стосовно числової осі 0...64к. Тому для перших команд число 7fffh (+32767) більше числа S000h (-32768), а для других число 7fffh (32767) менше числа S000h (32768). Аналогічно, команди для чисел із знаком вважають, що 0 більше, ніж Ffffh (-1), а команди для чисел без знаку - менше.

Розглянемо приклад використання команд умовних переходів для обробки символів. Хай ми вводимо з клавіатури деякий рядок символів (наприклад, ім'я файлу), і хочемо, щоб в програмі цей рядок був записаний прописними буквами, незалежно від того, які букви використовувалися при її введенні. Між іншим, при введенні з клавіатури команд   DOS   система завжди виконує цю операцію, тому і команди, і ключі, і імена файлів можна вводити як прописними, так і рядковими буквами -  DOS  у всіх випадках перетворить всі букви в прописних. 

code segment

assume cs:code,ds:data

main proc

mov Ax,data             ;Инициализация

move Ds,ax             ;Регистр DS

;Виведемо службове повідомлення

mov Ah,09h               ;Функция виводу

mov Dx,offset msg   ;Адрес повідомлення

int 21h

;Поставимо запит до DOS на введення рядка

mov Ah,3fh              ;Функция введення

mov Bx,0                   ;Дескриптор клавіатури

mov Cx,80                ;Ввод максимум 80 байт

mov DX, offset buf    ;Адрес буфера введення

int 21h

mov actlen,ax           ;Фактически введено

;Перетворимо рядкові російські букви на прописні

mov Cx,actlen           ;Длина введеного рядка

mov Si,0                     ;Указатель у буфері

filter: mov    Al,buf[SI];Возьмем символ

cmp Al,'a'                  ;Меньше 'a'?

jb  noletter                  ;Да, не перетворювати

cmp Al,'я'                 ;Больше 'я'?

ja noletter                  ;Да, не перетворювати

cmp Al,'п'                 ;Больше 'п'?

ja more                      ; Так, на подальшу перевірку

sub Al,20h               ;'a'..'п'. Перетворимо в прописну

jmp store                   ;На збереження в буфері

more: cmp Al,'p'      ;Меньше 'p1' (псевдографіка)?

jb noletter                   ;>'п',<'p'. Не змінювати

sub Al,50h          ;'p'...'я'. Перетворимо в прописну

store: mov   buf[SI],al      ;Отправим назад в buf

noletter: inc SI            ;Сместим покажчик

loop filter                    ;Цикл по всіх символах

; Виведемо результат перетворення на екран для контролю

mov Ax,40h       ;Функция виводу

mov Bx,1           ;Дескриптор екрану

mov Cx,actlen   ;Длина повідомлення

mov Dx,offset buf  ;Адрес повідомлення

int 21h

mov Ah,01          ;Остановим програму

int 21h                 ;в очікуванні натиснення клавіші

;Завершимо програму

mov Ax,4c00h

int 21h

main endp

code ends

data segment

msg db "Вводите!$"

buf db 80 dup (' ')            ;Буфер введення

actlen dw 0

data ends

stk segment stack

dw 128 dup(')

stk ends

end main 

На початку програми на екран виводиться службове повідомлення "Вводите!", яке служить запитом програми, адресованим користувачеві. Далі за допомогою функції  DOS  3fh виконується введення рядка тексту з клавіатури. Функція 3fh може вводити дані з різних пристроїв - файлів, послідовного порту, клавіатури. Різні пристрої ідентифікуються їх дескрипторами. При роботі з файлами дескриптор кожного файлу створюється системою в процесі операції відкриття або створення цього файлу, а для стандартних пристроїв - клавіатури, екрану, принтера і послідовного порту діють дескриптори, що закріплюються за цими пристроями при завантаженні системи. Для введення з клавіатури використовується дескриптор 0, для виводу на екран дескриптор 1.

При виклику функції 3fh в регістр  ВХ  слід зане сти необхідний дескриптор, в регістр DX - адреса області в програмі, виділені й для прийому символів, що вводяться з клавіатури, а в регістр СХ - максимальне число символів, що вводяться. Ми вважаємо, що користувач не вводитиме більше 80 символів. Можна ввести і менш е; у будь-якому випадку введення рядка слід завершити натисненням клавіші <Enter>. Функція 3fh, відпрацювавши, поверне в регістрі АХ реальне число введених символів (включаючи коди 13 і 10, утворювані при натисненні клавіші <Enter>). У прикладі 3.5 число введених символів зберігається в осередку actlen з метою використання далі по ходу програми.

Далі в циклі з actlen кроків виконується аналіз кожного введеного символу шляхом порівняння з межами діапазонів рядкових російських букв. Російські рядкові букви розміщуються в двох діапазонах код ASCII (а...п і р...с), причому для перетворення в прописних букв першого діапазону їх код слід зменшувати на 20h, а для перетворення букв другого діапазону - на 50h. Тому аналіз проводиться за допомогою чотирьох команд порівняння сmр і відповідних команд умовних переходів. Модифікований символ записується на те ж місце в буфері buf.

Після завершення аналізу і перетворення введених символів, виконується контрольне виведення вмісту buf на екран. Оскільки ми заздалегідь не знаємо, скільки символів буде введено, вивід на екран здійснюється функцією 40h, серед параметрів якої указується число символів, що виводяться. Так само, як і у разі функції введення 3fh, для функції виведення 40h в регістрі ВХ необхідно вказати дескриптор пристрою введення, в даному випадку екрану, а в регістрі DX - адреса рядка, що виводиться.

Коди символів є числами без знаку, і використання в даному випадку команд умовних переходів для чисел без знаку представляється логічним і навіть єдино можливим. Якщо, проте, уважно розглянути поняття больше- менше для чисел із знаком і без знаку, то легко побачити, що поки ми порівнюємо один з одним тільки "позитивні" або тільки "негативні" числа, команда ja еквівалентна команді jg, а команда jb еквівалентна команді jl. Проте при порівнянні, наприклад, код цифр з кодами російських букв, правильний результат можна отримати лише при використанні команд переходів для чисел без знаку. Втім, завжди наочніше і надійніше використовувати ті команди, які відповідають істоті даних даних, навіть якщо такий же правильний результат вийде і при використанні "неправильних" команд.
Виразніше різниця між числами із знаком і без знаку виявляється при використанні арифметичних операцій, наприклад, операцій множення або ділення. Тут для чисел із знаком і чисел без знаку передбачені окремі команди:

mul - команда множення чисел без знаку;

imul - команда множення чисел із знаком;

div - команда ділення чисел без знаку;

idiv - команда ділення чисел із знаком. 

Пояснимо відмінності цих команд на формальних прикладах.

;Множення позитивних чисел із знаком

mov Al,5    ;Первый співмножник дорівнює 5

mov Bl,7    ;Второй співмножник дорівнює 7

mul BL        ;AX=0023h=35

mov Al,5    ;Первый співмножник дорівнює 5

mov Bl,7    ;Второй співмножник дорівнює 7

imul BL       ;AX=0023h=35  

Обидві команди, mul і imul, дають в даному випадку однаковий результат, оскільки позитивні числа із знаком збігаються з числами без знаку. Не так йде справа при множенні негативних чисел.

;Множення негативних чисел із знаком

mov Al,ofch     ;Первый сомножітель=252

mov Bl,4             ; Другий співмножник =4

mul BL                  ;AX=03F0h =1008

mov Al,ofch     ;Первый сомножітель=-4

mov Bl,4              ; Другий співмножник =4

imul BL                ;AX=FFFO=-16 

Тут дія команд mul і imul над одними і тими ж операндами дає різні результати. У першому прикладі число без знаку Fch, яке інтерпретується, як 252, умножається на 4, даючи в результаті число без знаку 3F0, тобто 1008. У другому прикладі те ж число Fch розглядається, як число із знаком. В цьому випадку воно складає -4. Множення на 4 дає Fff0h, тобто -16.

загрузка...
Сторінки, близькі за змістом