Один з недоліків ТР полягає в тому, що створена на її основі виконавча програма (exe файл) менш ефективна за ту, яку можна отримати при використанні мови Асемблера.
Добре написана програма на Асемблері дозволяє сформувати виконавчу програму з малим часом виконання, і сама вона буде займати мало пам’яті. Тільки Асемблер може реалізувати всі технічні можливості комп’ютера. Програми на Асемблері ефективні в першу чергу за рахунок використання регістрів процесора для зберігання проміжних результатів при обчисленні виразів і тому, що грамотний програміст більш ефективно застосовує машинні команди, ніж це робить компілятор. В деяких випадках без можливостей Асемблера просто важко обійтись: складання невеликих по розміру резидентних програм, розробка різноманітних драйверів.
Однак, більшість програмістів не складає програм на такій чудовій мові. Це зв’язано з тим, що цей процес можна порівняти з працею „каторжанина”.
Треба знати багато команд і всі тонкощі їх застосування, розуміти роботу всіх пристроїв комп’ютера, а також мати багато терпіння, щоб порозумітися з залізом.
Вихід з такого становища знайдемо у поєднанні зручних можливостей мови Turbo Pascal та корисних конструкцій Асемблеру. Є 3-и варіанти такого сполучення: вставки фрагмента програми на Асемблері або в машиних кодах та підключення зовнішніх підпрограм на Асемблері.
Перед тим, як застосувати ці варіанти розглянемо деякі особливості персонального комп’ютера з точки зору Асемблера. Головним елементом ПЕОМ являється мікропроцесор, умовна схема якого представлена на мал.1
Регістри Сегментні Вказівники
даних регістри
АХ
ВХ
СХ
DХ
| |
Регістри даних призначені для зберігання операндів та результатів операцій. Кожен з них складається з двох частин по 8 розрядів, що дозволяє виконувати операції як з байтами так і з словами. Доступ до даних в регістрі значно швидший, ніж до пам’яті, тому при складанні програми їх вживають досить часто.
Сегментні регістри обслуговують окремі області пам’яті – сегменти.
Регістрові вказівники застосовують для роботи із стеком. (Стек – це спеціальним чином організована пам’ять для зберігання деяких значень, для яких діє правило – „останнім прийшов – першим пішов”).
В регістрі команд зберігається поточна команда під час її дешифрування та виконання.
В регістрі прапорів (його ще називають PSW – слово стану процесора) кожний розряд (прапор) відповідає за якусь ознаку виконаної операції; наприклад: прапор парності PF (біт номер 2) дорівнює одиниці, якщо в результаті операції отримано число з парною кількість одиниць.
Арифметико-логічний пристрій призначено для виконання арифметичних та логічних операцій.
Важливе питання для програмування на Асемблері пов’язано з вибором адресації даних. При регістровій адресації операнди розміщуються в регістрах (MOV AX, CX передає число з регістру CX до AX). Можна використовувати безпосередню адресацію, коли константа вказується в команді (MOV AX, R). При непрямій адресації в команді записують в квадратних дужках назву регістру, в якому зберігається адреса даного. (MOV AX, [BX]). Адресацію по базі зручно використовувати при роботі з записами (MOV AX, [BX] + 5). Для роботи з елементами масиву вживають пряму адресацію з індексуванням. (MOV DI, 2
MOV AL, X [DI]), де X – масив елементів довжиною 1 байт.
В результаті виконання цих команд, в регістр AL буде передано третій елемент масиву X.
Існують інші способи адресації. Складається враження, що в Асемблері можна дане взяти де завгодно та передати його куди завгодно. Але це не так. Не можна, наприклад, безпосередньо пересилати число з однієї комірки пам’ іншу, з одного сегментного регістра в інший, із пам’ті в сегментний регістр і т. д.
Розглянемо можливості стикування ТР з Асемблером.
1. Є процедура
Inline,
Параметром якої являються один або декілька елементів машинного коду, розділених символом „/”. Це дає можливість в текст програми мовою Pascal включити окремі машинні команди, які підвищують її ефективність. Наприклад Inline ($FA/):
Такий оператор установить прапор IF в нуль в регістрі прапорів, тобто буде заборонено обробку переривання. Це аналог асемблерної команди cli. Оператор Inline ($FB)/); дозволяє обробляти переривання (аналог команди sti). Такі оператори бувають корисними при заміні підпрограм стандартної обробки переривань на свої, а також при складанні резидентних програм. (Дізнатись машинний код – аналог тієї чи іншої команди Асемблера можна, скориставшись довідником всіх команд – Абель „Язык Ассемблера для IBM PC и программирование”).
Так для команди sti приведено об’єктний код 11111011. Запишемо його в шістнадцятирічній системі счислення - $FB.
Приклад. Написати фрагмент програми на Pascal з використанням оператора Inline, в якій обчислюється сума квадратів (а²+в²).
Program AV;
Uses CRT;
Var a, b: Byte;
c: word;
Function sum (a; b: Byte): word;
Begin
Inline
(
$8A / $46 / 04 {MOV AL, b}
$ F6 / $66 / 04 {MUL b}
$ 8B / $CB / {MOV CX, AX}
$ 8A / $46 / 06 / {MOV AL, a}
$ F6 / $66 / 06 / {MUL a}
$ 03 / $C1 / {ADD AX, CX}
)
MOV @ Result, AX
end
end;
Починаючи з версії 6.0 в мові ТР з’явилась можливість полегшити використання команд Асемблера, завдяки вбудованому Асемблеру. Цей Асемблер взагалі сумісний з мовою Turbo Asm. (TASM) та макроасемблером (MASM). Тепер в програмі можна записати фрагмент на Асемблері, або у вигляді асемблерного оператора, або асемблерної процедури.
В першому випадку достатньо перед фрагментом поставити оператор Asm, а після фрагменту End; Такий оператор ставлять в будь-якому місці програми, там де можна поставити оператори мови Pascal.
В самому фрагменті можна записувати команди по правилам мови Асемблера (тобто в одному рядку одна команда) або за правилами мови ТР (тобто через символ ;).
Коментар оформляють за допомогою фігурних дужок між командами (всередині розміщувати не можна). Крім звичайних (глобальних) міток вони описані в Label, а діють і в частині програми на мові Асемблері, можна вживати локальні мітки. Такі мітки починаються символом @ і діють тільки в асемблерному операторі (їх описувати не треба).
Вбудований Асемблер може звертатись до змінних, описаних за правилами Turbo Pascal, наприклад:
Var x: word;
.
.
.
Asm
Mov Ax, x {зн-ня змінної х передано до рег. Ах}
.
.
.
end;
В Асемблерній частині програми можна вживати директиви db, dw, dd і за правилами Асемблера працювити з відповідними даними. Ці дані зберігаються в сегменті коду, але такі області даних не можна позначити іменем (як це робиться в звичайному Асемблері).
Мають місце деякі обмеження на використання регістрів в асемблерному операторі. Не можна міняти регістри BP, SP, SS та DS, тому що ТР вживає їх для взаємодії з операційною системою. Перші три регістри обслуговують стек, а регістр DS утримує адресу сегменту даних. Значення інших регістрів на початку роботи фрагменту на Асем. не визначені. Розглянемо приклад програми з асемблерною вставкою. Треба ввести з клавіатури ціле додатнє число (розмір word) і виконати його нормалізацію – зсувати вліво всі розряди, поки в старшому розряді двійкової форми числа не з’явиться одиниця.
asm
mov ax, A {а передаємо до Ах}
mov cx, 25 {початкове значення лічильника циклу}
@ cikl: js @ kinec {якщо в знаковому розряді 1 – передача на кінець}
shl ax, 1 {зсув вліво}
loop @ cikl {цикл}
@ kinec: mov b, ax {резул. із Ах до В}
end;
writeln (‘b=’, b);
end.
Пояснення до програми.
Команда Mov Ax, A передає значення змінної А до регістру Ах, після чого лічильник циклу Сх приймає початкове значення. Тіло циклу складається з двох команд: js та shl. Перша з них перевіряє прапор SF в регістрі прапорів. Як тільки він установиться в одиницю (це станеться тоді, коли знаковий розряд регістру Ах стане рівним одиниці), управління буде передано на мітку @ kinec. Команда shl Ax, 1 виконує зсув числа в регістрі Ах на один розряд вліво. Після завершення циклу, нове число з регістру Ах пересилається до змінної В.
На результат впливає не абсолютне значення А, а взаємне розміщення двійкових одиниць в цьому числі. Це був приклад застосування асемблерного оператору.
Оператор asm можна використовувати також в процедурах та функціях. В цьому випадку автоматично генерується код входу та виходу із процедури чи функції. Щоб функція повертала правильний результат, кінцеве значення функції треба занести в спеціальну зміну @ Result.
Program Fibonacci;
Uses CRT;
Var
F_n, N: word;
Function Fib_n (N: word): word;
Begin
Mov Сx, N {Загрузити номер до лічильн. Сх}
Xor Ax, Ax {Обнулити аккамультор}
Jcxz @ 2 {Якщо лічильник = 0, то F_n =0 }
Mov Dx, 1 {Занести в Dx перший член ряду}
@1: ADD Ax, Dx {Одержати в Ах черговий член ряду}
XCHG Ax, Dx {Поміняти місцями 2-а остан. члена ряду}
Loop @ 1 {Якщо лічильник <>0, то продовжуємо}
@2: Mov @ Result, Ax {Результат запис. до @ Result }
end;
end
begin
clrscr;
write (‘Vv N’);
read (N);
F_n:= Fib_n (N);
Writeln (‘Fib_n =’, F_n);
End.
Тепер розглянемо приклад вживання асемблерної підпрограми.
Заголовок такої підпрограми має звичайний для мови ТР вигляд, за винятком того, що він закінчується словом Assembler. Це може бути процедура або функція. Тіло підпрограми складається з одного асемблерного оператора (asm…end). В тілі підпрограми можна виконувати операції з формальними/ фактичними параметрами. Якщо параметр проста змінна, його можна переслати до регістру, а якщо це складна змінна (масив, запис чи рядок) то пересилають її адресу.
Приклад:
Procedure kit (z: string; Var Rob: string; D, Nom: byte);
Assembler;
Asm
Les si, z {адресу вхідного параметра z переслати в si}
Lds di, rob {адресу вихідного параметра в di}
Mov bl, d {значення D в bl }
Mov cl, Nom {значення Nom в cl}
end;
Тепер всі дані доступні для роботи, результат обробки рудку z буде записано в рядок, адреса якого зберігається в регістрі Di. Якщо в списку параметрів перед іменем змінної стоїть слово Var, то для роботи з нею треба спочатку завантажити її адресу, а вже потім звертатись до пам’яті, на яку вона показує.
Приклад:
Procedure kit (z: string, Var Rob: string; Var D, Nom: byte);
Assembler;
Asm
Les si, z
Lds di, rob
Les bx, d
Mov ax, es : [bx]
Les bx, nom
Mov cx, es: [bx]
end;
Розглянемо приклад використання асемблерної процедури для швидкого множення цілих чисел.
В мові Асемблера є декілька команд для множення чисел, але всі вони виконуються досить довго.
Так команда Mul виконується за 118 тактів. „Швидке” множення застосоване на операціях зсуву та складання.
Program PA;
Var x, y, rez: word;
Procedure BM (a, b: word; Var r: word);
Assembler;
Asm
Mov ax, a {перепишемо а, b до регістрів}
Mov dx, b
Xor bx, bx {обнуляємо bx}
Mov cx, 7 {лічильник циклу}
test dx,1 {перевіряємо молодший розряд}
je @ m1 {якщо він = 0, зробимо перех.}
mov bx, ax {інакше в проміжну суму множене}
@ m1: shl ax, 1 {зсув множеного на один розряд вліво}
shr dx, 1 {зсув множеного на один розряд вправо}
test dx, 1 {перевіряємо молодший розряд}
je @ m2 {якщо там 0 - перехід}
add bx, ax {інакше збільшимо проміжну суму}
@ m2: loop @ m1 {цикл}
lds di, r {адресу r в di}
mov [di], bx {результат в r}
end;
Begin
Writeln (‘вв. Два цілих додатніх числа’);
Readln (x, y);
BM (x, y, rez);
Writeln (‘Rez=’, Rez : 4);
End.
В головній програмі вводяться 2-а цілих додатніх числа, і викликається асемблерна процедура ВМ. Спочатку числа передаються до регістрів Ах та Dх, а потім реалізується такий алгоритм: якщо молодший розряд множника (розряд Dх) дорівнює 1, то значення множеного (регістр Ах) додається до значення проміжної суми (регістр Вх).
Якщо в цьому розряді 0, операція складання не відбувається. Після цього багаторазово виконується цикл, в середині якого множене зсувується на один розряд вліво, множник на один розряд вправо, та знову перевіряється молодший розряд множника. Якщо там 1, то множник додається до проміжної суми, а якщо 0 – ні. Після закінчення циклу результат з регістру Вх пересилається до змінної r.
Для реалізації такого алгоритму використані декілька команд.
Команда Test Dx, 1 перевіряє молодший розряд в регістрі Dx. В результаті перевірки прапор ZF в регістрі прапорів установиться в одиницю, якщо в молодшому розряді Dx буде одиниця, інакше ZF установиться в 0.
Наступна команда Je перевіряє прапор ZF, якщо ZF = 0 відбувається перехід на вказану мітку, інакше на наступну команду. Команди Shl та Shr забезпечують зсув числа в регістрі відповідно вліво та вправо. Після кожного виконання команди Loop (команда організації циклу) лічильник в регістрі Сх зменшується на одиницю. Як тільки лічильник стане рівним нулю, команда Lds Di, z передасть адресу змінної r до регістру Di, а команда Mov забезпечить пересилання числа з регістра Вх за цією адресою. Необхідно звернути увагу на те, що цей алгоритм придатний тільки для множення додатніх чисел.
Процедури/функції з директивою Assembler мають наступні особливості в порівнянні з процедурами/функціями, які її не використовують:
- компілятор не генерує код для копіювання параметрів - значень в локальні зміні. Це впливає на всі параметри – значення, розмір яких не дорівнює 1, 2, 4 байтам. В середині процедури чи функції такі параметри повинні інтерпретуватися так як би вони були параметрами – змінними;
- компілятор не виділяє пам’ять для результата функції і зcилка на ідентифікатор @ Result буде помилковою. Функції з рядковим результатом є виключенням із цього правила;
- функції, які використовують директиву assembler, повинні повертати результат наступним чином:
а) результати функцій стандартного типу (Integer, Char, Boolean, перелічувальний типи) повертаються в регістрі AL для 8 – розрядних значень, а в регістрі Ax для 16 – розрядних значень, або в парі регістрів Dx, Ax для 32-розрядних значень;
б) результати функцій дійсного типу (Real) повертаються в регістрах Dx, Bx, Ax;
в) результати функцій типів, що використовують інструкції сопроцесора 8087 (Single, Double, Extended, Comp) повертаються в регістрі стека сопроцесора ST(0);
г) результати функцій вказівникового типу повертаються в парі регістрів Dx, Ax;
д) результати функцій рядкового типу повертаються в тимчасовій комірці, на яку вказує @ Result.
Розглянемо випадок приєднання до програми мовою ТР файла з розширенням .obj. В такому файлі зберігається підпрограма на Асемблері після стану трансляції. Для правильного стикування треба забезпечити виконання декількох вимог. В програмі мовою ТР підпрограма, написана на Асемблері, повинна бути описана як External (зовнішня).
Наприклад:
Function SU (M: mas; kol: integer): integer;
external;
Крім того, тут повинна знаходитися директива підключення об’єктного файла, наприклад {$ L SU. obj}
$ L –директива компілятора;
SU. obj – ім’я файлу.
В цій директиві можна вказати повний шлях до файлу або відповідно налагодити турбосередовище.
(Options/Directories/Object directories)
Директива {$F+} визначає використання „далекої” адресації. Виклик підпрограми на Асемблері виконується за звичайними правилами мови ТР. Структура підпрограми на Асемблері залежить від версії цієї мови (Tasm або Masm). При програмуванні на мові Tasm рекомендується вживати таку структуру підпрограми:
· Model Turbo PASCAL
· Code
ім’я п/п Proc Far список програм
Public ім’я п/п
Push ds
.
.
.
Pop ds
Ret
ім’я п/п endp
Code ends
end
Перша програма визначає модель пам’яті, вона виконує ряд операцій для підтримки стикування з мовою ТР. Команди . Code та Code ends визначають відповідно початок та кінець сегменту коду. В заголовку підпрограми вказують її ім’я, тип та список формальних параметрів.
Слово Far визначає так звану далеку адресацію, а слово Near – близьку. В першому випадку коди виконавчих операторів підпрограми та головної програми будуть розміщені в різних сегментах пам’яті, а в другому – в одному сегменті. В списку параметрів через кому перелічені імена формальних параметрів та їх типи. Команда Public забезпечує доступність до підпрограми в інших програмах. В зв’язку з тим, що у всіх програмах один сегмент даних, доступ до глобальних змінних відбувається через регістр DS. Для того, щоб в n/n на Асемблері. Можна було спокійно використовувати цей регістр, треба на початку підпрограми зберегти в стеку те значення DS, яке було сформовано в головній програмі (Push DS), а перед поверненням в головну програму це значення відновити зі стеку (Pop DS).
Команда Ret виконується останньою в підпрограмі. Кінець підпрограми обмежено командою Ends, а кінець всього тексту на Асемблері – командою End.
При такому способі зв’язку в підпрограмі можна виконувати операції над формальними параметрами. Прості змінні доступні звичайним чином, тобто їх можна передавати до регістру(або навпаки). Складні змінні(масив, рядок, запис)та змінні, які в програмі описані в списку параметрів зі словом Var, доступні через свою адресу. Для роботи з такими параметрами їх адреси передають до регістрів SI та DI. Приклад демонстраційної програми стикування з Асемблером, яка забезпечує введення/виведення масиву чисел в головній програмі, а підпрограма на Асемблері сортує цей масив.
Program A;
Const u=10;
Type mas=array [1…10] of integer;
{$ F+}
Procedure Sortobme(Var massiv1 :mas;kol:Integer); external;
{$ L Sortobme.obj} {підключення п/п}
Var i: integer; massiv:mas;
Begin
Writeln(’Вв - масив’);
For I : = 1 to n do
Read(massiv[I]);readln;
Sortobme(massiv, n); {виклик п/п}
For I : = 1 to n do
Write(massiv[I]:4);
Writeln;
End.