гос / sp-lect (1)
.pdfIFNDEF Name
ECHO &Name not defined
.ERR
ENDIF
полягає лише в тому, що за допомогою ECHO в IFNDEF можна сформувати розширений коментар, щодо причин завершення роботи препроцесора замість стандартного «error A2055 : forced error : symbol not defined : Name», що генерується .ERRNDEF.
Приклад 6.4 Наступний приклад демонструє використання деяких з вищерозглянутих макрозаcобів на прикладі задачі обробки одновимірного масиву. Додаток SumaMacro використовує чотири різні макровизначення для знаходження добутку елементів одновимірного масиву з елементами розміром в байт, слово і подвійне слово.
; Файл SumaMacro.inc
SumArray1 MACRO |
Name,bwd,reg1,reg2 |
|
LOCAL |
next |
|
push ecx |
|
|
push esi |
|
|
push eax |
|
|
xor ebx,ebx |
|
|
mov ecx,LENGTHOF Name |
||
lea esi,Name |
|
|
next: lods&bwd |
|
|
add reg1,reg2 |
|
|
loop next |
|
|
pop eax |
|
|
pop esi |
|
|
pop ecx |
|
|
ENDM |
|
|
SumArray2 MACRO |
Name,bwd,plus |
|
LOCAL |
next |
|
FOR Register,<eax,ecx,esi>
push Register
ENDM
xor ebx,ebx
mov ecx,LENGTHOF Name lea esi,Name
next: lods&bwd plus
loop next
FOR Register,<esi,ecx,eax>
pop Register ENDM
ENDM
SumArray3 MACRO Name,sum,type
LOCAL next
FORC r,cda
push e&r&x
ENDM
xor sum,sum
mov ecx,LENGTHOF Name lea edx,Name
next: add sum, type ptr [edx]
add edx,SIZE Name loop next
FORC r,adc
pop e&r&x
ENDM
ENDM
pushl MACRO list:VARARG
FOR operand,<list> push operand
ENDM
ENDM
popl MACRO list:VARARG
FOR operand,<list>
pop operand
ENDM
ENDM
SumArray4 MACRO Name
LOCAL next
IFB <Name>
ECHO Array name required
.ERR ENDIF IFNDEF Name
ECHO &Name not defined
.ERR ENDIF
pushl eax,ecx,esi xor ebx,ebx
mov ecx,SIZEOF Name / SIZE Name lea esi,Name
next:
IF SIZE Name EQ 1
lodsb
add bl,al
ELSEIF SIZE Name EQ 2
lodsw
add bx,ax ELSEIF SIZE Name EQ 4
lodsd
add ebx,eax
ELSE
.ERR ENDIF loop next
popl esi,ecx,eax
ENDM
; Файл SumaMacro.asm
.386 |
|
.model flat, stdcall |
|
option casemap :none |
|
include SumaMacro.inc |
|
.data |
|
ByteArray |
db 1,2,3,4,5 |
WordArray |
dw 1,2,3,4,5 |
DWordArray |
dd 1,2,3,4,5 |
.code |
|
start: SumArray1 ByteArray,b,bl,al SumArray1 WordArray,w,bx,ax SumArray1 DWordArray,d,ebx,eax SumArray2 ByteArray,b,<add bl,al> SumArray2 WordArray,w,<add bx,ax>
SumArray2 DWordArray,d,<add ebx,eax>
SumArray3 ByteArray,bl,byte
SumArray3 WordArray,bx,word
SumArray3 DWordArray,ebx,dword
SumArray4 ByteArray
SumArray4 WordArray
SumArray4 DWordArray ret
end start
7 МОДУЛЬНЕ ПРОГРАМУВАННЯ
7.1 Концепція модульного програмування
Уся історія розвитку зaco6iв розробки програм невід’ємно пов’язана з намаганнями максимально спростити i полегшити процес програмування. Особливу актуальність ці задачі набули внаслідок зростання обсягів і
складності програмного забезпечення. Одним із методів вирішення таких задач є модульне програмування.
Концепція модульного програмування передбачає розподіл складної задачі на ряд більш простих функціонально самостійних під задач з подальшою реалізацією кожної такої під задачі або їх окремих груп в окремих програмних одиницях – модулях. Модулі реалізують певну функціональність і пов’язуються між собою попередньо обумовленими вхідними та вихідними даними. Об’єднання окремих модулів в єдиний проект виконується спеціальними програмними засобами.
Використання модулів спрощує керування проектом і надає дві головні переваги:
1.Розробка та налагодження окремих модулів виконується ізольовано від інших модулів проекту, що дозволяє залучати до його розробки цілі
групи програмістів.
2.З’являється можливість на кожному з етапів проекту залучати до розробки модулів різну кількість програмістів різної кваліфікації й до того ж таких, що володіють різними мовами програмування.
Впершу чергу ми приділимо увагу питанням використання асемблерних модулів розроблених з використанням Microsoft Macro Assembler (MASM) в програмах, розроблених з використанням Microsoft Visual C++, але при цьому будуть задіяні й зворотні зв’язки, бо реалізація асемблерних модулів в свою чергу передбачатиме використання сервісних послуг операційної системи Windows NT, а її компоненти написані переважно на С/С++.
Модульне програмування є подальшим продовженням і розширенням процедурного програмування, і окремою структурною одиницею кожного модуля є підпрограма (процедура, функція).
При об’єднанні розроблених з використанням різних мов програмування окремих модулів в єдиний проект, їх сумісність забезпечується дотриманням при розробці підпрограм модулів певних конвенцій виклику та іменування.
7.2 Конвенції виклику
Конвенції виклику (Calling convention) регламентують способи організації виклику підпрограм, передавання їм параметрів і повернення результатів роботи підпрограм в точку виклику. Конвенції виклику визначають:
1.Місце розташування параметрів підпрограми: а) регістри; б) стек;
в) регістри та стек;
2.Місце розташування повернених результатів.
3.Порядок передавання параметрів
а) при використанні регістрів – зіставлення регістрів з номерами параметрів в описі підпрограми;
б) при використанні стека – прямий (зліва направо) або зворотний (справа наліво) порядок зіставлення параметрів в описі підпрограми;
4.При використанні для передавання параметрів стеку – хто вирівнює стек по завершенні виклику
а) підпрограма; б) код, що викликав підпрограму;
5.Вміст яких регістрів підлягає обов’язковому збереженню і поновленню в підпрограмі.
Протягом розвитку програмування сформувалося декілька конвенцій виклику серед яких можна зазначити наступні:
1.PASCAL – параметри передаються через стек в прямому порядку (зліва направо), стек вирівнює підпрограма.
2.С (CDECL) і SYSCALL – параметри передаються через стек в зворотному порядку (справа наліво), стек вирівнює код, що викликає підпрограму.
3.STDCALL – параметри передаються через стек в зворотному порядку (справа наліво), стек вирівнює підпрограма.
4. FASTCALL (REGISTER) – не стандартизована і існує в декількох реалізаціях:
а) в реалізації Borland для 32-х розрядних версій Windows NT: перший (зліва направо) параметр в регістрі EAX, другий в EDX, третій в ECX, інші, за наявності таких, розміщуються в стеку в прямому порядку (зліва направо), стек вирівнює підпрограма;
б) в реалізації Microsoft для 32-х розрядних версій Windows NT: перший (зліва направо) параметр в регістрі ECX, другий в EDX, інші, за наявності таких, розміщуються в стеку в зворотному порядку (справа наліво), стек вирівнює підпрограма;
в) в реалізації Microsoft для 64-х розрядних версій Windows NT: для параметрів розміром 8/16/32/64 біта – 1- й (зліва направо) в RCX, 2-й в RDX, 3-й в R8, 4-й в R9. Якщо перші чотири параметри є числами з плаваючою комою, то вони передаються зліва направо в регістрах XMM0–XMM3. П’ятий та інші параметри, за наявності таких, розміщуються в стеку в зворотному порядку (справа наліво, тобто від останнього до п’ятого), стек вирівнює код, що викликає підпрограму;
Конвенції CDECL, STDCALL і FASTCALL для 32-х розрядних версій
Windows NT передбачають повернення результату з підпрограми через регістр- акумулятор за значенням в EAX/AX/AL, якщо його розмір не перевищує 32 біти. Результат розміром 8 байтів повертається за значенням в парі регістрів EDX:EAX, все інше повертається за адресою в EAX. Числа з плаваючою комою повертаються за значенням через верхівку стеку арифметичного співпроцесора.
Серед перерахованих вище, конвенції виклику PASCAL (FORTRAN) і SYSCALL не підтримується сучасними версіями компіляторів Microsoft Visual C++, а MASM безпосередньо не підтримує FASTCALL.
7.3 Конвенції іменування
Функції усередині програм на мовах C й C++ розпізнаються за внутрішніми іменами які в загальному випадку відрізняються від імен функцій
увихідному тексті програми. Внутрішнє ім'я являє собою символьний рядок, що створюється компілятором при компіляції прототипу або визначення функції. У більшості випадків не потрібно знати внутрішнє ім'я функції. Лінкер й інші інструменти зазвичай можуть обробляти ім'я, що не є внутрішнім, однак
удеяких ситуаціях потрібно знати і вміти вказувати внутрішнє ім'я.
Набір правил за якими компілятором створюється внутрішнє ім’я функції називають конвенцією іменування (Naming convention). Певній конвенції виклику відповідає певна конвенція іменування. Таблиця 7.1 на прикладі функції, визначеної з іменем FunctionName, демонструє як с(по)творюються імена функцій компілятором.
Таблиця 7.1
Конвенція виклику |
Ім’я функції (конвенція іменування) |
|
|
|
|
SYSCALL |
FunctionName |
|
|
|
|
PASCAL, FORTRAN, BASIC |
FUNCTIONNAME |
|
|
|
|
С (__cdecl) |
_FunctionName |
|
|
|
|
STDCALL (__stdcall) |
_FunctionName@N, де N – |
загальний розмір в |
|
байтах параметрів функції |
|
|
|
|
__fastcall |
@FunctionName@N, де N – |
загальний розмір в |
|
байтах параметрів функції |
|
|
|
|
7.4 Підпрограма як структурна одиниця модуля
Коли в попередніх розділах зазначалося, що Microsoft Macro Assembler чи Microsoft Visual C++ не підтримує ту або іншу конвенцію виклику чи іменування, то малося на увазі те, що вони не мають відповідних вбудованих мовних засобів, які б примусили компілятор чи асемблер автоматично сформувати код функції та/або код її виклику згідно з конвенцією.
Для асемблера таким мовним засобом є розширений синтаксичний опис підпрограми, який для 32-розрядного MASM має наступний вигляд:
ім’я PROC [<Атрибути>] [USES <Регістри>] [<Параметри>]
[LOCAL <Змінні>]
<послідовність команд>
RET
ім’я ENDP
В квадратних дужках «[…]» зазначено необов’язкові директиви. <Атрибути> мають наступний синтаксис:
[<відстань>] [<конвенція>] [<видимість>] [<пролог>]
NEAR |
С |
|
|
|
|
FAR |
STDCALL |
PRIVATE |
NEAR16 |
SYSCALL |
|
<відстань> = |
<конвенція>= |
<видимість>= PUBLIC |
FAR16 |
PASCAL |
|
NEAR32 |
BASIC |
EXPORT |
|
|
|
FAR32 |
FORTRAN |
|
Якщо атрибут відстані не зазначений – він визначається асемблером автоматично на основі моделі пам’яті, зазначеної у відповідному параметрі директиви .MODEL. Для моделі FLAT атрибут відстані буде NEAR і його можна не зазначати.
Атрибут <конвенція> визначає конвенцію виклику (та іменування) згідно з якою асемблер буде генерувати код прологу, і, як наслідок, код епілогу підпрограми.
Якщо атрибут конвенції не зазначений – він визначається асемблером автоматично на основі конвенції, зазначеної у відповідному параметрі директиви .MODEL (STDCALL для .MODEL FLAT, STDCALL).
Якщо конвенція виклику не зазначена ні в директиві .MODEL, ні в описі підпрограми, то використання усіх можливостей розширеного синтаксису опису підпрограми стає неможливим.
Атрибути конвенції PASCAL, BASIC і FORTRAN є абсолютно еквівалентними. Також еквівалентними в плані конвенції виклику є атрибути конвенції C і SYSCAL, але вони розрізняються конвенцією іменування.
Атрибут <видимість> визначає, чи буде підпрограма доступна (PUBLIC та EXPORT) для виклику з інших програм або модулів, чи не буде (PRIVATE). Останній атрибут <пролог> надає деякі можливості щодо керування генерацією асемблером коду прологу та епілогу підпрограми. Цей атрибут для 32-х розрядних додатків вважається застарілим.
<Регістри> – визначає для директиви USES перелік розділених пробілом імен регістрів загального призначення, які будуть автоматично зберігатися на вході у підпрограму і поновлюватися на виході.
<Параметри> являють собою розділений комами перелік параметрів
підпрограми, що має синтаксис Параметр:тип параметру [, … [,\]]
Параметр – символьне ім’я формального параметру підпрограми.
BYTE (SBYTE)
WORD (SWORD)
DWORD (SDWORD, REAL4)
тип параметру=
QWORD (REAL8)
визначений за допомогою TYPEDEF
VARARG
Для перенесення параметрів переліку на наступний рядок використовується символ «\» після коми.
Директива LOCAL дозволяє визначити локальні змінні для використання в підпрограмі. Пам’ять під локальні змінні резервується в стеку підпрограми і область їх видимості – тільки код підпрограми. <Змінні> в директиві LOCAL являють собою розділений комами перелік, що має синтаксис
Змінна[[кількість]]:тип змінної [, ………………]
Тобто перелік складається із елементів з синтаксисом двох типів: Змінна:тип змінної – для змінних простих та структурованих типів
(але не масивів).
Змінна[кількість]:тип змінної – для масивів з елементами простих та структурованих типів.