Макрозасоби асемблера Програмування, уроки та приклади
Сучасні асемблери містять у собі так звані макрозасоби і тому називаються іноді макроассемблерів. Загальна ідея макрозасобів у тому, що включенням у вихідний текст програми пропозицій спеціальної мови макрозасобів (макромови) ми певною мірою керуємо процесом трансляції програми. Макромова дозволяє виконувати або не виконувати трансляцію окремих ділянок програми залежно від деякої нами ж умови, що визначається (умовна трансляція); здійснювати розмноження ділянки вихідного тексту програми, у тому числі з модифікацією кожного повторення (блоки повторення); включати до програми написані окремо фрагменти з налаштуванням їхнього тексту відповідно до заданих параметрів (макрокоманди). Об'єкти, створювані з допомогою директив макромови, зазвичай називають макросами. Іноді, щоправда, термін макрос відносять лише одного конкретному виду макрозасобів, саме, до макрокоманде. Використання макросів спрощує складання вихідного тексту програми і іноді робить цей текст наочнішим, хоча в окремих випадках, як, наприклад, у разі директив умовної трансляції, навпаки, може призвести до істотного ускладнення вихідного тексту. Як і в будь-якій мові програмування, в мові макрозасобів є багато різного роду тонкощів, але в прикладному програмуванні найчастіше використовуються лише базові можливості цієї мови. Тому ми обмежимося тут розглядом основних макрозасобів асемблера. Блоки повторення Блоки повторення змушують транслятор повторити заданий блок вихідного тексту вказану кількість разів. Блок, що повторюється, може складатися з директив опису даних (і тоді він включається до складу сегмента даних) або з команд процесора (і тоді він описується в програмному сегменті). Наприклад,наступний фрагмент сегмента даних дозволяє утворити масив, що складається з кодів ASCII великих українських букв:
sym='A' ;Початкове значення тимчасової змінної
symbols: ;Ім'я масиву для посилань на нього
rept 32 ;Повторювати стільки разів
db sym ;Повторювана директива
sym = sym + l; Зміна змінної
endm ;Кінець блоку повторення
Як очевидно з наведеного фрагмента, блок повторення починається з директиви асемблера rept (від repetition, повторення), а закінчується директивою endm (end macro, кінець макросу). Реально у сегменті даних виділяється 32 байт, заповнених числами від 81h до 9Fh, які передбачається розглядати як послідовність українських букв. Того самого результату можна було досягти за допомогою наступної пропозиції:
або простіше, хоч і менш наочно:
symbols db 128,129,130,131 і т.д. до числа 159
Макрос повторення дещо скорочує час, потрібний для опису в тексті програми необхідного масиву, хоча, можливо, знижує наочність цього опису. При підключенні до комп'ютера вимірювального або керуючого обладнання іноді виникає необхідність уповільнити роботу процесора під час звернення до портів цього обладнання. Уповільнення здійснюється включенням до тексту програми однієї або, якщо потрібно, кількох команд безумовного переходу на таку пропозицію:
in AL,300h; Перше звернення до обладнання
jmp a ;Затримка на час
a: jmp b ;виконання
b: jmp з; трьох команд jmp
c: in AL,301h ;Наступне звернення до обладнання
Для того, щоб не створювати багато непотрібних, по суті, міток, такі пропозиції часто записують наступним чином:
in AL, 300h; Перше звернення до обладнання
jmp $+2;Затримка на час
jmp $+2 ;виконання
jmp $+2; трьох команд jmp
in AL,301h ;Наступне звернення до обладнання
Це, мабуть, простіше ніж писати 6 команд jmp. Макроси повторення мають кілька різновидів, які ми тут не розглядатимемо. Макрокоманди Програми, написані мовою асемблера, часто містять повторювані ділянки тексту з однаковою структурою. Таку ділянку тексту можна оформити як макровизначення, що характеризується довільним ім'ям і необов'язковим списком формальних аргументів. Після того, як таке визначення зроблено, поява у програмі рядка, що містить ім'я макровизначення та список фактичних аргументів (все це разом називають макрокомандою), призводить до генерації всього необхідного тексту, що називається макророзширенням. Варіюючи фактичні аргументи, можна, зберігаючи постійною структуру макророзширення, змінити окремі його елементи. Макровизначення має починатися рядком з ім'ям макровизначення та директивою macro, у полі аргументів якого вказується список формальних аргументів. Закінчується макровизначення директивою endm. Нехай у програмі потрібно неодноразово зберігати в стеку вміст трьох регістрів, але в кожному конкретному випадку номери регістрів та їх порядок відрізняються. Оформимо ці дії у вигляді макровизначення:
Поява у вихідному тексті програми рядка
призведе до створення наступного фрагмента тексту:
Якщо ж у вихідному тексті є рядок
то відповідне макророзширення матиме вигляд:
Як фактичні аргументи можуть виступати будь-які позначення асемблера, допустимі для цієї команди. Зокрема, макровиклик
призведе до наступного макророзширення:
Якщо якісь рядкимакровизначення повинні бути позначені (наприклад, з метою організації циклів), то позначення міток слід оголосити локальними за допомогою оператора local. У цьому випадку асемблер, генеруючи макророзширення, створюватиме власні позначення міток, які не повторюються при повторних викликах однієї і тієї ж макрокоманди:
point: loop point
Макрос delay створює затримку фіксованої тривалості. Якщо до тексту програми включити дві макрокоманди delay
то їх макророзширення, підставлені в текст програми, виглядатимуть так:
При повторних підстановках макровизначення транслятор замінює позначення мітки point на позначення 0000, 0001 і т.д., забезпечуючи тим самим правильне виконання команд циклів і переходів. Макрокоманди схожі з підпрограмами щодо того, що в обох випадках ми описуємо деякий програмний фрагмент один раз, а звертаємося до нього багаторазово, можливо, з передачею різних параметрів. Однак ці обчислювальні засоби розрізняються як за способом використання, так і своїми можливостями. Підпрограми дозволяють скоротити обсяг здійснюваного файлу за рахунок опису повторюваних ділянок програми лише одного разу. При кожному виклик підпрограми командою call відбувається перехід на той самий фрагмент програми, що містить підпрограму, а після виконання підпрограми - повернення назад в точку виклику. Текст підпрограми повністю визначається на етапі її написання, і зміни в ході виконання підпрограми можливі лише за рахунок передачі тих чи інших конкретних значень. Механізм використання макросу інший. Кожна макрокоманда, що зустрілася транслятору в тексті програми, замінюється на повний текст макровизначення. Якщо макрокоманда містить параметри, то у процесіцієї заміни відбувається підстановка параметрів до тексту макровизначення. Утворене таким чином макророзширення становить частину тексту програми, не відрізняється від інших пропозицій програми і не потребує будь-яких викликів. У силу цих обставин макрокоманди виявляються дещо ефективнішими за підпрограми за швидкістю виконання, особливо, якщо врахувати час, необхідний для підготовки параметрів перед викликом підпрограми (наприклад, проштовхування їх у стек). Навряд чи варто, проте, проводити таке порівняння. Підпрограми та макрокоманди мають різні галузі застосування. Підпрограми служать для скорочення обсягу програми, підвищення її наочності і спрощення перебудови алгоритму виконання всього програмного комплексу шляхом зміни складу і порядку підпрограм, що викликаються. При цьому активне використання підпрограм може зменшити розмір програми в десятки разів. Сенс використання макрокоманд зовсім інший. Макрокоманди дозволяють спростити процес написання програми та, можна сказати, є засобом автоматизації програмування. При цьому мова макрокоманд надає великі можливості зміни тексту макророзширення в залежності від параметрів, що вказуються в макрокоманді. Проілюструємо ці можливості простому прикладі макрокоманди виведення на екран символу. Такою макрокомандою можна користуватися в процесі налагодження складних програм, щоб отримувати інформацію про вміст будь-яких осередків пам'яті. Приклад оформлений у вигляді закінченої програми, яка має суто демонстраційний характер.
Мал. 2.18. Виведення програми 2.1.
;Макрокоманда endpr завершення програми endpr macro ;Макрокоманда без параметрів mov AX,4C00h int 2 In endm ;Кінець макрокоманди ;Макрокоманда delay програмної затримки, що настроюється. delay macro time ;Параметр - число кроків locallabell,Iabel2 ;Локальні мітки push CX ;Збережемо зовнішній лічильник mov CX,time ;Отримаємо фактичний параметр Iabel2 : push CX ;Збережемо його у стеку mov CX, 0 ;Нехай буде 64К кроків labell: loop lanell ;Внутрішній цикл pop CX ;Вилучимо зовнішній лічильник loop Iabel2 ;Зовнішній цикл pop CX ;Відновимо CX програми endm ;Кінець макрокоманди
Для того, щоб транслятору були доступні макрокоманди з файлу MYMACRO.MAC, його слід на етапі трансляції приєднати до вихідного тексту програми директивою асемблера include:
Усі макрокоманди, включені у цей файл, можна використовувати у будь-якому місці програми. Директиви умовної трансляції Директиви умовної трансляції (умовного асемблювання) дозволяють мати у вихідному тексті програми різні варіанти окремих фрагментів програми, та шляхом завдання певних умов керувати процесом трансляції. Таким чином можна, наприклад, включати або виключати з тексту програми службові, фрагменти налагодження або налаштовувати програму для виконання на заданому процесорі. Нехай, наприклад, у процесі налагодження складної програми ми використовуємо підпрограму regs виведення на екран вмісту всіх регістрів процесора. Включаючи в різні місця програми виклик цієї підпрограми, ми маємо можливість контролювати хід її виконання, у тому числі й такі тонкі моменти, як, наприклад, розташування програми в пам'яті або інтенсивність використання стека. Для управління процесом трансляції передбачимо константу debug (налагодження), ненульове значення якої вимагатиме налагоджувального варіанта трансляції, а нульове - робочого. Початок програми, а також ділянки з викликом підпрограми налагодження будуть виглядати наступним чином:
;debug=l;Видалить символ ';'для налагоджувальної трансляції
;debug=0 ;Видалить ';' для робочої трансляції
if debug ;Транслювати тільки якщо debug=l
call regs; Виклик налагоджувальної підпрограми
endif ;Кінець блоку умовної трансляції
if debug ;Наступне включення налагоджувального блоку
Зрозуміло, можна налагоджувати програму у налагоджувальному варіанті, а потім видалити всі виклики допоміжної підпрограми regs вручну і отримати робочий варіант, проте на практиці зазвичай (або навіть завжди) виявляється, що після експлуатації програми протягом деякого часу в ній виявляються непомічені раніше помилки призводить до необхідності знову вставляти до неї налагоджувальні рядки. Часто цю процедуру доводиться повторювати багаторазово. Використання в програмі директив умовної трансляції скорочують процедуру перетворення програми з налагоджувального варіанту на робочий або навпаки до операції стирання одного символу ";" на початку програми та усувають ймовірність випадкового внесення у програму нових помилок у процесі видалення або вставки налагоджувальних рядків. Розглянемо ще один приклад застосування директив умовної трансляції. Як зазначалося, сучасні процесори надають програмісту значну кількість додаткових команд, які можна використовувати у програмах реального режиму, але, зрозуміло, якщо комп'ютер оснащено відповідним процесором. Неважко скласти універсальну програму', яку можна виконувати як на сучасних процесорах (у більш ефективному режимі), так і на більш старих (з деякою втратою ефективності), якщо включити до неї директиви умовної трансляції цих додаткових команд. До таких команд, зокрема, відносяться команди збереження у стеку всіх регістрів загального призначення pusha та відновлення всіхрегістрів рора. Наведемо приклад умовної трансляції цих команд, у якому використовується конструкція макромови if. else. endif:
i386=l if i386 .386 endif code segment use16 assume CS:code main proc … if i386 push ;Збереження всіх регістрів однією командою else push AX push CX push DX push BX push BP push SI push DI endif . . . ;Використання регістрів після ;збереження їх значень if 1386 рора ;Відновлення всіх регістрів однією командою else pop DI pop SI pop BP pop BX pop DX pop CX pop AX endif