22 ПРОГРАМУВАННЯ МОВОЮ АССЕМБЛЕР
Мова Ассемблер (МА) - мова програмування низького рівня (машинно-орієнтована мова).
Існують мови програмування високого рівня і низького рівня (машинно-орієнтовані, асемблер).
Основу Асемблера складають команди мікропроцесора або мікроконтролера, які не можна змінити, а можна вивчити і використовувати під час програмування. Тому говорять, що Асемблер орієнтований на конкретний мікропроцесор або мікроконтролер (на його систему команд).
Окрім цих команд, які при компіляції перетворюються у машинний код, програма включає псевдокоманди та директиви, які вказують програмі компілятора, яка також зветься асемблером, як виконати відповідні дії по створенню виконуваного файлу для мікропроцесора.
Нагадаємо, що при створенні робочих програм на МА використовують такі керуючі програми:
транслятори - перетворюють програму, що написана на одній мові програмування, в програму, що написана на іншій мові програмування;
компілятори - є різновидом транслятора; переводять програму з мови високого рівня в машинні коди;
асемблери - це окремий випадок компілятора; перетворює програму з мови асемблера в двійкові (машинні) коди (ДК);
відлагоджувачі - керуюча програма для відлагодження програми у відповідній системі.
Це питання розглянемо на прикладі мови TASM мікропроцесора І8086.
Програма на мові асемблер містить два типи виразів:
команди, що транслюються в машинні коди;
директиви, що керують ходом трансляції.
Вираз має вигляд:
{<мітка>}: <мнемокод> {<операнд>} {,} {< операнд >} {; коментар}.
У фігурних дужках наведено елементи виразу, яких може не бути у деяких командах. Мітка, мнемокод і операнди відокремлюються хоча б одним пробілом або табуляцією. Максимальна довжина рядка становить 132 символи, проте найчастіше використовуються рядки з 80 символів, що відповідає довжині екрана.
Прикладами команд асемблера є:
Мітка Мнемокод Операнд(и) Коментар
MOV АХ, 0 ; Команда, два операнди
Ml: ADD АХ, ВХ ; Мітка, команда, два операнди
DELAY: MOV СХ, 1234 ; Мітка, команда, два операнди
Прикладом директиви є:
Мітка Мнемокод Операнд(и) Коментар
COUNT: DB 1 ; Мітка, команда, один операнд
Мітка у мові асемблера є символічною адресою команди. Мітками позначаються не всі команди, а лише ті, до яких треба виконувати перехід за допомогою команд переходів або викликів підпрограм. У командах переходів або викликів підпрограм позначення мітки використовується як операнд - символічна адреса переходу, наприклад:
Мітка Мнемокод Операнд(и) Коментар
JMP МІ ; Перехід до команди з міткою МІ
CALL DELAY ; Виклик підпрограми з міткою
; DELAY
Після мітки ставиться двокрапка. Першим символом у мітці має бути літера або один із спеціальних символів: знак питання «?»; крапка «.»; знак амперсанд «@»; підкреслювання «_»; знак долара «$». Знак питання і крапка можуть займати тільки перше місце. Максимальна довжина мітки - 31 символ. Приклади міток: COUNT, PAGE25, $£10. Рекомендується використовувати описові та смислові мітки. Усі мітки у програмі мають бути унікальними, тобто не може бути декількох команд з однаковими мітками. Як мітки не можна використовувати зарезервовані асемблером слова, до яких належать коди команд, директиви, імена регістрів. Наприклад, імена АХ, DI та AL є зарезервованими і використовуються тільки для зазначення відповідних регістрів.
Мнемокод ідентифікує команду асемблера. Для мнемокодів використовують скорочені або повні англійські слова, які передають значення основної функції команди: ADD - додати, SUB (SUBtract) - відняти, XCHG (eXCHanGe) - поміняти.
Операнди відокремлюються комами. Якщо задано два операнди, то перший з них завжди є приймачем, а другий - джерелом інформації. Команда може містити різну кількість операндів різних типів, наприклад:
Мітка Мнемокод Операнд(и) Коментар
RET МІ ; Повернутися (операнди не вказані)
INC СХ ; Збільшити СХ (один операнд)
ADD АХ, 12Н ; Додати 12Н до вмісту АХ
; (два операнди)
MOV ВХ, [SI] ; Занести до регістра ВХ число
; з комірки пам'яті з адресою
; DS:SI (два операнди)
Коментарі ігноруються у процесі трансляції і використовуються для документування і кращого розуміння змісту програми. Коментар завжди починається із символу «;» і може містити будь-які символи. Коментар може займати увесь рядок або бути розташованим за командою в одному рядку, наприклад:
Мітка Мнемокод Операнд(и) Коментар
; Цей рядок є коментарем
ADD АХ, ВХ ; Команда і коментар в одному рядку
Оскільки коментарі не транслюються у машинні коди, то їх кількість не впливає на ефективність виконання програми.
Програма мовою асемблер називається початковою програмою або початковим програмним модулем. Асемблювання або переведення початкової програми у машинні коди виконує програма-транслятор, наприклад TASM.COM. Залежно від установок, які задає користувач, програма переводить початковий модуль в один із двох програмних модулів: командний модуль (файл з розширенням .СОМ) або об’єктний модуль (файл з розширенням .OBJ).
Командний модуль містить машинні коди команд з абсолютними адресами і виконується МП. Командний модуль доцільно використовувати у тих випадках, коли ємність програми не перевищує розміру одного сегмента (64 кбайт). Першим оператором командного модуля є директива ORG 100Н (ORIGIN - початок), яка розміщує першу команду програми у сегменті кодів зі зміщенням 100Н. Закінчуватися програма має або командою RET, або стандартною процедурою коректного виходу до MS DOS:
Мітка Мнемокод Операнд(и) Коментар
MOV AH, 4СН ; Занести у АН число 4СН
; (значення параметра переривання
; INT 21Н)
INT 21Н ; Викликати стандартну процедуру
; переривання 21Н - коректного
; виходу до MS DOS
Останнім записом програми має бути директива END.
1. Створення вихідної програми
Користуючись одним з екранних редакторів персонального комп'ютера (ПК) згідно з вимогами до написання асемблерних програм у ЕХЕ-форматі, створюється вихідна програма Prog.asm ( рис. 22.1).
2. Асемблерування вихідної програми
За допомогою системної програми TASM.EXE (турбоасемблер) вихідна програма Prog .asm перетворюється (асемблерується, компілюється, транслюється) у машинний код, відомий як об'єктна програма Prog.obj (рис. 22.1).
Для цього в командному рядку диска користувача, наприклад D:, набирається і виконується команда: Tasm.exe /1 /zi Prog.asm.
3. Компонування об'єктної програми
Після асемблерування вихідної програми за допомогою системної програми TLINK.EXE виконується компонування об'єктної програми (рис. 22.1).
Файл Prog.obj містить тільки машинний код у шістнадцятковій системі числення. Тому що програма може завантажуватися майже в будь-яке місце пам'яті для виконання, то асемблер може не визначити всі машинні адреси. Крім того, можуть використовуватися інші програми (підпрограми) для об'єднання з основною. Тому призначенням програми TLINK завершення визначення адресних посилань і об'єднання (якщо потрібно) декількох програм і створення програми, що виконується: Prog.exe.
Для цього в командному рядку диска користувача, наприклад D:, набирається і виконується команда: Tlink.exe /V Prog.obj (рис. 22.1).
Рисунок 22.1 – Схема асемблерування, компонування, налагодження і виконання програми
4. Налагодження програми
Після одержання файлу, що виконується, Prog.exe можливе його налагодження (трасування) з метою пошуку і виправлення некоректно працюючих ділянок програми - пошуку логічних помилок.
Для входу в налагоджувач асемблера необхідно в командному рядку диска користувача, наприклад D:, набрати і виконати команду: Td.exe Prog.exe (рис. 22.1).
5. Запуск ЕХЕ - програми
Після завершення зазначених вище дій здійснюється виконання файлу Prog.exe.
6. Одержання лістингів програм
Після налагодження програми, що розроблюється, можна одержати і роздрукувати її лістинг. Для цього в команду, що виконує асемблерування вихідної програми, включають ключ: l, що викличе формування файлу Prog .lst.
Варто звернути увагу, що ніякі директиви (псевдокоманди) асемблера, а також коментарі, написані після крапки з комою, не генерують машинних кодів.
Лістинг містить не тільки вихідний текст програми, але також у лівій частині трансльовані машинні коди в шістнадцятковому форматі. Ліворуч від машинних кодів розташовані шістнадцяткові адреси команд і даних.
Описану послідовність створення і виконання ЕХЕ - програми відображає рис. 22.1.
Замість того, щоб розробляти нову програму з нуля, корисно використовувати шаблон, що містить найнеобхідніше для написання ЕХЕ - програм (рисунок 22.2).
Рисунок 22.2 – Оболонка (шаблон) для ЕХЕ – файлів
Більшість програм мовою асемблера можна розділити на п'ять основних частин:
- заголовок;
- макровизначення;
- сегмент даних;
- тіло програми;
- закінчення.
1 Заголовок
Програма на мові асемблера починається з заголовка. У ньому містяться команди і директиви, що не приводять до створення машинного коду при трансляції. Вони вказують асемблеру, як виконати визначені дії, генеруючи файл, що виконується.
На рисунку 22.3 представлено простий заголовок, характерний для більшості ЕХЕ - програм. Це фрагмент програми, так що не намагайтеся його транслювати.
Не обов'язковий рядок %TITLE описує призначення програми, і при роздрукуванні її в Turbo Asemblег текст, що знаходиться в лапках, буде розташовуватися на початку кожної сторінки вихідного тексту. Директива IDEAL переводить Turbo Asemblеr у режим Ideal.
Якщо програма написана на Microsoft Macro Asembler (MASM), директиву Ideal потрібно опустити.
Рисунок 22.3 – Типовий заголовок асемблерної програми
Далі йде директива MODEL, що вибирає одну з декількох моделей пам'яті (таблиця 22.1), багато з яких використовуються при написанні комбінованих асемблерних програм з мовами С або Pascal. Найбільш вдалим вибором при програмуванні мовою асемблера є small (мала) модель пам'яті. Мала модель пам'яті виділяє програмі 64 Кбайт - для коду, що виконується, і 64 Кбайт - для даних, і дозволяє створювати програми розміром до 128 Кбайт, що практично є межею ефективності у світі машинних кодів.
Таблиця 22.1 – Моделі пам'яті
Директива STACK на рисунку 22.3 резервує простір для стека програми - області пам'яті, у якій містяться два типи даних: проміжні значення, що записуються підпрограмами чи передаються їм, а також вміст регістрів процесори при організації обслуговування переривань.
2. Макровизначення
Після заголовку програми йдуть різні описи констант і змінних. У мові асемблера константи часто називають макровизначеннями, що використовують директиву EQU, яка зв'язує значення з ідентифікатором (визначеним ім'ям, замість якого в програмі Turbo Assembler підставить відповідне значення). Винятково для числових значень, крім директиви EQU, можна застосовувати знак рівності (=).
Макровизначення можуть розташовуватися в будь-якому місці програми, але щоб ваша програма легко читалася, розміщуйте макровизначення відразу після заголовка програми.
Використання ідентифікаторів макровизначень замість “магічних” чисел 0l00h або 0B00h дозволяє звертатися за ім'ям до виразів, рядків та інших величин, що спрощує програму для читання і налагодження.
Нижче подано декілька прикладів макровизначень, які можуть йти за заголовком, наведеним на рисунку 22.4:
Рисунок 22.4 – Приклади макровизначень
Незважаючи на те, що більшість імен макровизначень просто замінюється відповідними значеннями і виразами - аналогічно використанню констант у мовах Pascal і С, існує кілька суворих правил, які треба пам'ятати при створенні і використанні макровизначень у мові асемблера:
- після опису імені константи за допомогою директиви EQU не можна змінювати його значення. Перевизначення цієї константи в рівності не допускається (наприклад, зміна значення Count на 11);
- це обмеження не поширюється на імена-ідентифікатори, описані за допомогою знака рівності (=), – ви можете вільно змінювати їхні значення. Зверніть увагу, як у розглянутому вище прикладі значення Size змінюється з 50 на 0. Це можна здійснювати в будь-якому місці програми, а не тільки в секції рівностей;
- EQU може описувати всі типи рівностей, включаючи числа, вирази і символьні рядки. Знак рівності може описувати тільки числові рівності, що складаються з чисел 10 чи 0Fh або виразів: Count*Size чи Address+2;
- ідентифікатори макровизначень рівностей не є змінними - ні вони, ні їхні значення не містяться в сегменті даних програми. Команди асемблера не можуть змінити значення ідентифікатора в макровизначеннях, незалежно від того, чи були вони описані за допомогою директиви EQU чи знака = ;
- хоча можете розташовувати макровизначення в будь-якому місці програми, але краще віддавати перевагу їх розміщенню на початку тексту. Макровизначення, розташовані глибоко в тексті програми, можуть стати джерелом помилок, що важко виявляються;
- вирази, описані за допомогою EQU, обчислюються, коли відповідне ім’я-ідентифікатор використовується програмою. Вирази, описані через знак рівності (=), обчислюються безпосередньо в місці визначення. Асемблер зберігає текст EQU-виразу, а для виразу, описаного за допомогою знака рівності, - тільки його значення.
Для кращого розуміння останнього правила розглянемо кілька прикладів. Припустимо, що є три макровизначення:
LinesPerPage = 66,
NumPages = 100,
TotalLines = LinesPerPage* NumPages.
Очевидно, що TotalLines дорівнює добутку LinePerPage і NumPages, чи 6600. У більшості мов програмування зірочка означає множення. Оскільки TotalLines описано за допомогою знака рівності (=) – мається на увазі числове значення, вираз обчислюється відразу і результат ставиться у відповідність TotalLines.
Якщо де-небудь у програмі ви зміните значення NumPages, значення TotalLines залишиться тим же. Ситуація зміниться, якщо ви опишете TotalLines за допомогою EQU:
TotalLines EQU LinePerPage*NumPages.
У цьому випадку Turbo Assembler збереже не значення, що обчислюється, а дійсний текст виразу, що йде за директивою EQU (у нашому випадку - текст виразу LinePerPage*NumPages). Пізніше в програмі, коли будете використовувати TotalLines, асемблер вставить цей текст так, начебто ви набирали його в цьому місці вихідного тексту, потім вираз буде обчислено і замінено кінцевим значенням. Якщо перед цим ви змінили значення одного чи обох ідентифікаторів, що використовуються у виразі (NumPages чи LinePerPage), отриманий результат також відповідно зміниться.
Цей вплив на значення виразу макровизначення може в ряді випадків виявитися корисним. Ви можете створити один програмний модуль, на значення виразів макровизначень якого будуть впливати макровизначення іншого модуля. Потрібно розуміти тонку різницю між макровизначеннями, визначеними за допомогою знака рівності і директиви EQU. Їхнє недбале використання також може приводити до виникнення помилок.
3. Сегмент даних
Сегмент даних звичайно розташовується між визначеннями і командами програми. Можна, хоча це рідко буває корисним, визначати сегмент даних у довільному місці програми чи мати кілька сегментів даних у різних частинах програмного тексту. Незважаючи на ці можливості, ваша асемблерна програма буде простіша для розуміння і модифікації, якщо ви наслідуєте простим рекомендаціям, запропонованим нижче, визначаючи усі свої змінні між макровизначеннями і кодом, що виконується.
Сегмент даних вашої програми повинний починатися з директиви DATASEG. Вона дає вказівку асемблеру розмістити в пам'яті змінні, зазначені в сегменті даних програми, що може досягати 64 Кбайт для малої моделі пам'яті. Сегмент даних може містити два типи змінних: ініціалізовані і неініціалізовані. При виконанні програми ініціалізовані змінні мають визначені значення, які ви визначили в тексті програми, і містяться у файлі програмного коду на диску. Ці змінні автоматично завантажуються в пам'ять і доступні для читання при виконанні програми. Неініціалізовані змінні аналогічні ініціалізованим, за винятком того, що вони не займають простору у файлі, що виконується, і, отже, мають невизначені значення при виконанні програми. Внаслідок цього визначення великої неініціалізованої змінної для резервування в пам'яті великого буфера, заповнюваного з дискового файлу, не приводить до збільшення розміру файлу програми, що виконується.
Для того, щоб неініціалізовані змінні не містилися в об'єктному коді, вони повинні описуватися після останньої ініціалізованої змінної у вихідному тексті програми. Неініціалізовані змінні, описані між іншими ініціалізованими змінними, будуть займати місце в об'єктному коді, тим самим збільшуючи на диску розмір файлу, що виконується.
Резервування простору під змінні
Розглянемо типовий сегмент даних, що може йти після заголовка програми і макровизначень:
DATASEG
numRows DB 25,
numColumns DB 80,
videoBase DW 0B00h
Першою йде директива DATASEG, що інформує асемблер про необхідність виділення простору в пам'яті під сегмент даних програми. Потім визначено три змінні: numRows, numColumns і videoBase. Як правило, рекомендується записувати константи (Count, NumPages тощо) з великої літери, а змінні - з малої літери. Це необов'язкова угода, ви можете набирати символи на власний розсуд або великими, або малими літерами. Деякі програмісти використовують символ підкреслення для поліпшення прочитання назв змінних, що складаються з декількох слів, наприклад, записуючи num_rows і video_base замість злитого написання, що використано тут.
DB (визначити байт) і DW (визначити слово) - дві найчастіше використовувані директиви, що застосовуються для виділення простору під відповідні змінні програми. На відміну від мов високого рівня, у яких розташування змінних у пам'яті звичайно не є важливим, у мові асемблера необхідно виділяти простір у пам'яті під змінні і, у випадку ініціалізованих змінних, приписувати значення цьому простору. Відчуйте, наскільки це відрізняється від ідентифікаторів макровизначень, що асоціюються із змінними виключно у вихідному тексті програми. Для них, на відміну від змінних, не виділяється простір у сегменті даних пам'яті.
Імена змінних (numRows, numColumns і videoBase) є позначками, що вказують на ділянки пам'яті, що виділяються - у даному випадку, на простір, що резервується під значення змінних. Програма може звертатися до цього простору, використовуючи позначку як покажчик на відповідне значення в пам'яті. В асемблерних програмах позначки перетворюються в адреси пам'яті, за якими містяться змінні, що дозволяє вам звертатися до пам'яті за іменами, а не за чисельними адресами.
При програмуванні безпосередньо в машинних кодах ви повинні замість позначок визначати дійсні адреси. Використання символьних позначок для визначення розташування змінних у пам'яті є однією з основних переваг мови асемблера.
Змінні розташовуються безпосередньо одна за одною. Знаючи це, можна чинити різні “штуки”. Наприклад, здається, що визначення
DATASEG
аТОm DB “ABCDEF GHIJKLM”
nTOz DB “NOPQRSTUVWXYZ”
створюють два символьних рядки з позначками аТОш і nTOz. Однак у пам'яті символи від А до Z зберігаються послідовно, створюючи один рядок букв алфавіту. Позначка nTOz просто вказує на середину рядка, при цьому в пам'яті не створюються два окремих фрагменти.
DB має спеціальну можливість визначати багатобайтові значення від одного до необхідної вам кількості байтів. Рядок складається з окремих ASCII-символів, кожний з яких займає один байт, отже, директива DB є в асемблері простим інструментом для визначення рядків символів і байт, розділяючи їх комами:
DATASEG
perfectTen DB 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
theTime DB 9,0 ; тобто 9:00,
theDate DB 12, 15, 98 ;тобто 12/15/1998.
Крім того, можна комбінувати символьні і байтові значення, створюючи дворядкові змінні з ASCII-символами повернення каретки (13) і кінця рядка (10). Наступний приклад показує, що символьні рядки можуть міститись як в одинарних, так і в подвійних лапках:
combo DB ‘Line#l’, 13, 10, “Line#2”
Деякі мови, зокрема Pascal, розрізняють одинарні символи і рядки символів. В асемблері символи і рядки відрізняються винятково довжиною. В асемблерних рядках відсутні такі допоміжні значення як байт довжини чи символ кінця рядка, якщо, звичайно ви самі не розмістили їх там.
Рядки розглядаються окремо при вивченні команд асемблера, спеціально створених для роботи з ними. Зараз необхідно запам'ятати, що на відміну від мов високого рівня, рядки в асемблері є просто набором послідовних значень у пам'яті, створених директивою DB.
4. Тіло програми
Після сегмента даних розташовується тіло програми, відоме за назвою кодового сегмента - області пам'яті, що містить код асемблерної програми, яка виконується. Всередині цієї області можна виділити чотири стовпчики тексту: позначки, мнемоніки, операнди і коментарі. Кожен стовпчик має свою важливу функцію, яку краще розглянути на прикладі. Кількість пропусків між колонками в тексті програми довільна. Найчастіше стовпчики вирівнюють за допомогою одно- чи дворазового натискання клавіші табуляції в редакторі.
Якщо ваш редактор дозволяє вибирати між вставкою символів табуляції і вставкою пропусків, використовуйте табуляцію, задавши її в кожному восьмому стовпчику, що є стандартною установкою для більшості редакторів. Вставка символів табуляції дозволяє легко вирівнювати стовпчики. При цьому ви зможете повторно редагувати текст кожного стовпчика, не зміщуючи текст в інших колонках. Звичайно, при бажанні, ви можете вставляти і пропуски між колонками - асемблер ніяких розходжень при цьому не робить.
Розглянемо приклад сегментів даних і коду і виділимо в ньому чотири стовпчики (рисунок 22.5).
Рисунок 22.5 – Чотири стовпчики в програмі мовою асемблера
Це незакінчена програма, так що не намагайтеся її транслювати. Вона містить основні елементи кодового сегмента мови Асемблера. У сегменті даних для подальшого використання визначається однобайтова змінна з ім'ям exCode зі значенням 0.
Після директиви CODESEG нa рисунку 22.5 представлено кілька рядків, розділених на позначки, мнемоніку, операнди і стовпчик коментарів. У першому стовпчику знаходяться дві позначки - Start: і Exit:. Позначки позначають місця в програмі, на які можуть посилатися інші команди і директиви. Для рядків без позначок цей стовпчик не заповнюється. У кодовому сегменті позначка завжди закінчується двокрапкою (:), у сегменті даних двокрапка не використовується (наприклад, у позначці exCode). Просто запам’ятаєте це правило, за яким не криється будь-який логічний зміст.
В другому стовпчику містяться мнемоніки. Під кожним мнемонічним формулюванням у цьому стовпчику ховається одна машинна команда: mov - для Move (пересилання даних), jmp - для Jumр (безумовний перехід), int - для Interrupt (переривання) і т. ін. Деякі мнемоніки запам’ятовуються легко: dec - для Decrement (декремент), shl - для Shift left (зсув вліво), ror - для Rotate Righ t (циклічний зсув вправо). Інші здаються справою рук божевільної друкарки: jcxz - для Jump if сх is Zero (перехід, якщо СХ дорівнює 0) і rcr - Rotate thr ough Carry Right (циклічний зсув вправо через перенесення). У деяких випадках мнемоніка цілком збігається з командою: out - для Out (виведення байта чи слова до порту), push - для Push (занесення слова до стека), pop - для Pop (діставання слова зі стека).
Третій стовпчик на рисунку 22.5 містить операнди, що обробляються мнемонічними командами. Деякі команди не вимагають операндів, у цьому випадку третій стовпчик залишається порожнім. Багато команд вимагають двох операндів, інші - тільки одного. Жодна з команд процесора і8086 не вимагає більше двох операндів. Перший операнд звичайно називається призначенням, другий - джерелом. Операнди можуть мати різний вигляд і вивчаються при розгляді кожної конкретної мнемонічної команди.
Четверта (остання) колонка є необов'язковою і, якщо включається в програму, то повинна починатися з крапки з комою (;). Turbo Asembler ігнорує всі символи від крапки з комою до кінця рядка, надаючи вам місце для розміщення короткого коментаря, що описує виконувані в даному рядку дії. При роботі обов'язково додавайте чіткі коментарі, які цілком описують вашу програму. Саме так треба починати, особливо, якщо мова асемблера є новою для вас, оскільки програми, написані на ній, є дуже складними для читання.
Іноді ви можете побачити рядок в асемблерній програмі, що починається з крапки з комою відразу в першому стовпчику. Такі, більш довгі коментарі часто використовуються програмістами для опису пояснювальним коментарем:
5. Закінчення
Останньою частиною програми на мові Асемблер є закінчення - рядок, що інформує Turbo Assembler про досягнення кінця програми. У кінці використовується єдина директива END. Повторюючи останній рядок (рисунок 22.5), розглянемо типовий кінець програми:
ЕND Start ; Кінець програми / Точка входу.
Директива END позначає кінець тексту вихідної програми. Асемблер ігнорує будь-який текст нижче цього рядка і, між іншим, тут можна розмістити додаткові коментарі. Праворуч від директиви ЕND ви маєте визначити позначку, з якої ви хочете розпочати виконання програми. Зазвичай ця позначка співпадає з позначкою, яка вказує на першу команду, що йде за директивою CODE SEG.
Ви можете розпочинати виконання програми з будь-якого місця, хоча для цього навряд чи існують особливі причини.
Префіксні команди
Машинні команди становлять більшу частину кодів асемблерної програми. В полі операції (мнемоніки) може бути присутня префіксна команда. Наприклад, команда STOSB (Store String Byte - записати рядок байтів) може включати префіксну команду REP (Repeat - повторити). У цьому випадку команди REP STOSB можуть бути закодовані в полі операції одного асемблерного твердження.
Перепризначення сегментів
Зазвичай звернення до змінних за прямою адресою виконуються відносно сегмента, який адресується за допомогою регістра ds. Щоб це змінити, можна визначити сегментне перепризначення в такий спосіб:
mov ch, [es:OverByte].
Ця команда завантажує в регістр ch байт з позначкою OverByte, записаний у сегменті, який адресується регістром es. Команда перепризначення сегмента ds на es потрібна для того, щоб змінити використовувану процесором за умовчуванням адресацію базового сегмента ds. Можна також використовувати аналогічні перепризначення і для доступу до даних в інших сегментах. Нижче наводиться три відповідних приклади:
mov dh,[cs:CodeByte] ; dh < - байт із кодового сегмента ,
mov dh,[ss:StackByte] ; dh < -байт зі стекового сегмента,
mov dh,[ds:DataByte] ; dh < - байт із сегмента даних ???.
У першому рядку в dh завантажується байт із кодового сегмента. Оскільки більшість змінних знаходиться в сегменті даних, звернення до даних, записаних у кодовому сегменті, є вкрай рідким. Другий рядок завантажує байт із сегмента стека. Хоча це і припустимо, але на практиці відбувається нечасто. У третьому рядку зайво визначається сегмент ds (дані за прямою адресою завжди за умовчуванням розташовуються в сегменті, який адресується регістром ds). Нижче наведено кілька додаткових порад, які допоможуть вам правильно використовувати перепризначення:
- незважаючи на те, що ви визначаєте перепризначення як частину звернення до даних, у дійсності воно займає байт машинного коду і вставляється тільки перед командою, на яку впливає. Перепризначення є командними префіксами і змінюють поводження наступної команди, що виконується;
- перепризначення діє тільки в межах однієї команди. Потрібно використовувати перепризначення при кожному зверненні до даних, розташованих у сегментах, відмінних від установлених за умовчуванням для даної команди;
- у режимі Ideal Turbo Assembler адреса, включаючи сегментне перепризначення, цілком береться в дужки. Хоча режим MASM забезпечує більш вільний стиль, прості синтаксичні вимоги режиму Ideal цілком сумісні з режимом MASM;
- ви повинні бути впевнені, що змінні дійсно розташовуються в обумовлених вами сегментах, і стежити, щоб адреси цих сегментів знаходилися в сегментних регістрах es і ds. Регістр стека ss і регістр кодового сегмента cs ініціалізації не вимагають.
При програмуванні мовою асемблера дуже часто доводиться повторюватися, переписуючи ті ж самі послідовності команд. Щоб зменшити кількість повторів у програмі групу команд можна зберігати в іменованому макросі і потім використовувати його всякий раз, коли вам необхідно виконувати визначену послідовність команд. Коли Turbo Asembler зустрічає макроім'я, він заміняє його послідовністю команд із макросу. Макрос можна визначити або в будь-якому місці програми, або розмістити в окремому текстовому файлі, а потім завантажувати цей файл під час асемблерування. Щоб це зробити, необхідно на початку асемблерної програми вставити директиву включення макросу INCLUDE «macros.mac» (прочитати бібліотеку макросів) (рисунок 22.6).
Рисунок 22.6 – Вихідна програма керування динаміком ПК (sound2.asm)
Найпростіший макрос (рисунок 22.7) починається з директиви MACRO, після чого вказується його ім'я, а в разі потреби, і його параметр.
У програмі SOUND2.asm (рисунок 22.6) макрос використовується для формування затримки часу.
Рисунок 22.6 – Макрос встановлення затримки
У записі “MACRO delay time” (рисунок 22.5) delay є ім'ям макросу, a time - його параметром. Значення цього параметра в програмі, яка використовує зазначений макрос, приймає конкретне значення. Так у розглянутій вихідній асемблерній програмі (рисунок 22.5) макрос підключається командою delay [len_snd], де len_snd - ім'я (значення) змінної, яка визначає тривалість звучання.
Щоб створювати локальні позначки, що автоматично нумеруються, усередині макросу, необхідно використовувати директиву LОСАL з ім'ям позначки. Директиву необхідно вставляти після рядка, що відкриває макрос.
У макросі “delay time” використана позначка “outer”, що є “локальною”, тому визначена відповідною директивою асемблера “local”. Компілятор замість позначки “outer” при першому використанні макросу поставить позначку “??0000”. Якщо цей макрос буде використаний ще раз, то компілятор замість позначки “outer” поставить позначку “??0001”. Тобто будуть створюватися різні локальні позначки, що дозволить багаторазово використовувати той самий макрос різними програмами і при цьому не буде виникати конфлікт позначок.
У наведеній вище програмі (рисунок 22.5) у макрос входять команди (блоки 5-14 схеми алгоритму (рисунок 22.7), які реалізують затримку часу, що визначає тривалість звучання динаміка.
Рисунок 22.7 – Схема алгоритму керування динаміком ПК
На рисунку 22.8 наведено лістинг розглянутої програми, який показує підключення макросу при асемблеруванні.
Рисунок 22.8 – Лістинг програми SOUND 2.asm(SOUND_2.1st)
Одним з найбільш корисних інструментів у мові асемблера є процедура, чи підпрограма - набір взаємозалежних команд, які зазвичай виконують одну операцію, що часто зустрічається. Процедура може виводити на екран рядок символів, підсумовувати набори значень чи ініціалізувати вихідний порт. Деякі підпрограми живуть чудовим життям: грають у шахи чи забезпечують доступ до віддаленого комп'ютера.
Інші мають більш скромну роль: виводять окремий символ чи зчитують код клавіші з клавіатури.
Деякі програмісти створюють довгі підпрограми, що виконують відразу велику кількість роботи, пояснюючи це тим, що використання численних маленьких підпрограм може сповільнювати швидкість виконання програми. Не робіть цього.
Поєднуючи команди у великі підпрограми, ви можете одержати тільки незначне збільшення швидкості, але, що більш імовірно, це призведе до помилкової і важко налагоджуваної програми, розмірковуючи над якою ви можете пошкодувати про те, що стали програмістом.
Гарна підпрограма виконує одну і тільки одну роботу. Гарна підпрограма коротка, наскільки це можливо, і велика, наскільки це необхідно. Гарна підпрограма уміщається на одній чи двох друкованих сторінках. Гарна підпрограма починається не з коду, а з коментарів, що описують її призначення, результати, очікувані вхідні дані і використовувані регістри. У гарній підпрограмі можна розібратися, не знаючи, що робить вся програма. Іншими словами, гарна підпрограма - коротка, витончена і точна.
У програмі керування динаміком ПК SOUNDl.asm (рисунок 22.9) наведено приклад використання підпрограми (процедури) DELAY для формування затримки часу, що визначає тривалість імпульсу і паузи (tiмп= tп) імпульсної послідовності, яка викликає звучання динаміка.
На рисунках 22.10, 22.11 і 22.12 відповідно наведено:
- схему алгоритму керування динаміком;
- схему алгоритму затримки часу;
- лістинг програми SOUND1.asm (SOUND1.1st).
Рисунок 22.9 – Вихідна програма керування динаміком (SOUND 1.asm)
Рисунок 22.10 – Схема алгоритму керування динаміком, якщо шпаруватість Q = 2
Рисунок 22.11 – Схема алгоритму затримки
Рисунок 22.12 – Лістинг програми керування динаміком SOUND_1.asm (SOUND_1.lst)