Anda di halaman 1dari 395

399

Ion IVAN Paul POCATILU Doru CAZAN

Coordonatori

PRACTICA DEZVOLTĂRII
SOFTWARE
ÎN
LIMBAJE DE ASAMBLARE

Editura Economică
Bucureşti 2002
400

Colectivul de autori

Ilie ADRIAN
Laurenţiu ARICIU
Doru CAZAN
Cristian CODREANU
Valentin DRAGOMIR
Ion IVAN
Laur IVAN
Alexandru LEAU
Adrian LICURICEANU
Zsolt MARK
Teodor MIHAI
Paul POCATILU
Mihai POPESCU
Gabriel ŞUTAC
Sebastian TCACIUC
Daniel VERNIŞ
401

CUPRINS
1 Introducere 11
2 Caracteristicile limbajelor de asamblare 13
2.1 Prelucrări elementare 13
2.2 Caracterul neimplicit al definirilor si prelucrărilor 14
2.3 Secvenţialitatea instrucţiunilor 16
2.4 Absenţa priorităţilor 17
2.5 Contextul dinamic 19
2.6 Libera poziţionare 20
2.7 Adresarea generalizată 21
2.8 Necompactitatea construcţiilor 23
2.9 Incluziunea 24
2.10 Concluzii 25
3 Reprezentarea informaţiei 27
3.1 Codificarea informaţiei 27
3.2 Organizarea datelor 28
3.3 Reprezentarea informaţiei numerice 30
3.4 Reprezentări ale şirurilor de caractere 36
4 Moduri de adresare 39
4.1 Calculul adresei unui operand 39
4.2 Modul de adresare imediată 41
4.3 Modul de adresare registru 43
4.4 Adresarea directă 44
4.5 Adresarea indexată 45
4.6 Adresarea bazată 47
4.7 Adresarea bazată şi indexată 48
402
4.8 Adresarea indirectă 49
5 Indicatorii de condiţie 53
5.1 Registrul FLAGS 53
5.2 Operaţii cu indicatorii de condiţie 55
5.3 Poziţionarea indicatorilor de condiţie la execuţie 56
5.4 Interacţiunea indicatori de condiţie - instrucţiuni 56
6 Instrucţiuni 61
6.1 Clasificarea instrucţiunilor 61
6.2 Descrierea instrucţiunilor 63
6.3 Forma externă a instrucţiunilor 65
6.4 Forma internă a instrucţiunilor 71
6.5 Comentariile 77
6.6 Efectele execuţiei instrucţiunilor 78
7 Definirea structurilor de date 81
7.1 Date elementare 81
7.2 Masive unidimensionale 82
7.3 Masive bidimensionale 82
7.4 Articolul 84
7.5 Variabile pointer 88
7.6 Variabile enumerative 89
7.7 Tabele de date 90
7.8 Obiecte 91
8 Implementarea structurilor fundamentale 95
8.1 Programarea structurată 95
8.2 Structura liniară 95
8.3 Structura alternativă 96
8.4 Structura repetitivă standard 99
8.5 Structura repetitivă condiţionată posterior 102
8.6 Structura alternativă multiplă 103
9 Aritmetici binare 107
9.1 Aritmetica binară pe 8 biţi 107
9.2 Aritmetica binară pe 16 biţi 109
9.3 Aritmetica binară pe 32 biţi 112
10 Aritmetici zecimale 117
10.1 Ajustări 117
10.2 Adunarea şi scăderea 119
10.3 Înmulţirea şi împărţirea 120
10.4 Proceduri de calcul 122
10.5 Determinarea lungimii rezultatului evaluări unei expresii aritmetice 129
11 Întreruperi 131
11.1 Întreruperi interne şi externe 131
11.2 Modul de funcţionare al întreruperilor 131
11.3 Tipuri de întreruperi 134
11.4 Exemplu de folosire al întreruperilor 138
11.5 Concluzii 145
12 Macrodefiniţii 147
12.1 Structura macrodefiniţiei 147
403
12.2 Macroapelul şi macroexpandarea 149
12.3 Etichete locale 151
12.4 Variabile locale 153
12.5 Macrodefiniţii derivate 155
12.6 Macrodefiniţii recursive 156
12.7 Redefinirea macrodefiniţiilor 157
12.8 Macrodefiniţii uzuale 157
12.9 Concluzii 163
13 Proceduri 165
13.1 Reutilizabilitatea 165
13.2 Structura unei proceduri 166
13.3 Apelarea procedurilor 168
13.4 Locul procedurilor 172
13.5 Instrucţiuni specifice lucrului cu proceduri 173
14 Prelucrări în virgulă mobilă 179
14.1 Scurtă istorie a prelucrărilor în virgulă mobilă 179
14.2 Resurse 179
14.3 Setul de instrucţiuni 182
14.4 Sintaxa instrucţiunilor 187
14.5 Forma externă a instrucţiunilor 189
14.6 Exemple 190
14.7 Concluzii 193
15 Lucrul cu şiruri de caractere 195
15.1 Caracteristicile instrucţiunilor de lucru cu şiruri de caractere 195
15.2 Setul de instrucţiuni pentru lucrul cu şiruri de caractere 196
15.3 Lucrul cu şiruri de caractere fără a folosi instrucţiuni specializate 198
15.4 Prefixul REP 199
15.5 Compararea a două şiruri 201
15.6 Poziţia unui subşir intr-un şir 203
15.7 Eliminarea spatiilor 204
15.8 Copierea unui şir sursă în şir destinaţie 205
15.9 Stabilirea lungimii unui şir 205
15.10 Conversie de la întreg binar la hexazecimal ca şir de caractere 206
15.11 Înlocuirea apariţiilor unui caracter 207
15.12 Concatenarea a două şiruri 207
15.13 Iniţializarea unui şir 209
15.14 Copierea unui şir dintr-o zonă de memorie într-o altă zonă de memorie 210
15.15 Numărarea caracterelor identice cu un caracter specificat 211
16 Fişiere 213
16.1 Fişiere, articole, identificatori 213
16.2 Operaţii cu fişiere 215
16.3 Utilizarea de fişiere pentru calculul fondului de salarii 221
16.4 Operaţii cu directori si subdirectori 231
17 Programarea mixtă C - limbaj de asamblare 235
17.1 Programul principal este scris în limbaj de asamblare, 235
procedurile apelate sunt scrise în limbajul C
404
17.2 Programul principal este scris în limbajul C, 242
procedurile apelate sunt scrise în limbaj de asamblare
17.3 Concluzii 251
18 Dezvoltarea de aplicaţii orientate obiect in limbaj de asamblare 253
18.1 Concepte folosite în dezvoltarea programelor orientate obiect 253
18.2 Definirea obiectelor prin structuri 257
18.3 Definirea obiectelor prin macrodefiniţii 260
18.4 Folosirea specificaţiilor proprii limbajului de asamblare 276
18.5 Analiza comparativă a variantelor de implementare a obiectelor 282
19 Structuri de programe 289
19.1 Programul ca singură secvenţă (0, 0, 1) 289
19.2 Programul din secvenţe distincte grupate într-un segment (1, 1, 1) 296
19.3 Proceduri incluse în segmentul programului principal (1, 1, 1) 300
19.4 Definiri distincte pentru toate componentele (m, k, n) 304
19.5 Structuri liniare de program 306
19.6 Structura arborescentă 307
19.7 Structuri de tip reţea 316
19.8 Concluzii 318
20 Optimizarea programelor 319
20.1 Criterii de optim 319
20.2 Cicluri maşină 320
20.3 Volumul de operaţii 323
20.4 Secvenţe echivalente 327
20.5 Alegerea tipului de dată 329
20.6 Eliminarea subexpresiilor comune 330
20.7 Gestionarea corectă a invarianţilor 331
20.8 Regruparea ciclurilor 332
20.9 Eliminarea secvenţelor inconsistente 334
20.10 Eliminarea secvenţelor inactive 334
20.11 Reacoperirea segmentelor 335
20.12 Alocarea optimă a regiştrilor 336
20.13 Concluzii 338
21 Designul limbajelor de asamblare 339
21.1 Cerinţe ale designului 339
21.2 Structura instrucţiunii limbajului de asamblare 340
21.3 Designul corectiv pentru limbajele de asamblare 342
21.4 Stabilirea numărului de registre 344
21.5 Concluzii 345
22 Elemente de grafica 347
22.1 Istoric al adaptoarelor grafice 347
22.2 Rutine BIOS şi moduri grafice 349
22.3 Lucrul în mod grafic folosind un adaptor VGA 351
22.4 Lucrul în mod grafic folosind un adaptor SVGA 359
22.5 Concluzii 364
23 Programe rezidente 365
23.1 Caracteristicile programelor rezidente 365
23.2 Întreruperi pentru programe rezidente 366
405
23.3 Sistemul de operare MS-DOS 369
23.4 Resurse ale programelor rezidente 370
23.5 Controlul proceselor 373
23.6 Probleme specifice în realizarea programelor rezidente 378
23.7 Activarea programelor rezidente 385
23.8 Program rezident pentru afişarea ceasului 388
23.9 Concluzii 398
24 Programarea în modul protejat 399
24.1 Moduri de operare ale procesorului 80x86 399
24.2 Regiştrii procesoarelor i386/i486 400
24.3 Moduri de gestiune a memoriei 407
24.4 Comutarea in modul protejat 409
24.5 Implementarea modului de lucru multitasking 423
24.6 Concluzii 426
25 Programarea aplicaţiilor Windows în limbaj de asamblare 427
25.1 Interfaţa de programare a aplicaţiilor Windows 427
25.2 Organizarea memoriei in Windows 9x 428
25.3 Încărcarea programelor Win32 430
25.4 Structura unui program în limbaj de asamblare 430
25.5 Programarea sub Windows 432
25.6 Asamblarea si editarea de legături 441
25.7 Exemplu de program Win32 441
Bibliografie 447
Anexe
A I Arhitectura procesoarelor din familia Intel 80x86 A-1
A II Mnemonicele instrucţiunilor procesorului 80x86 A-5
A III Setul de instrucţiuni ale procesoarelor Intel 80x86 A-11
A IV Regiştrii coprocesorului 80x87 A-69
A V Mnemonicele instrucţiunilor coprocesorului 80x87 A-73
A VI Primul program în limbaj de asamblare A-77
A VII Analiza comparată a utilizării diferitelor tipuri de reprezentări A-89
A VIII Calcul matriceal - studiu de caz A-101
406

2
CARACTERISTICI ALE LIMBAJELOR DE
ASAMBLARE
2.1 Prelucrări elementare

Limbajele algoritmice de programare (FORTRAN, COBOL, PL/I,


ALGOL) şi limbajele evoluate de programare (C++, PASCAL) conţin construcţii
care realizează prelucrări complexe. Compilatoarele lor dezvoltă secvenţe cu multe
instrucţiuni în module obiect. În schimb, limbajele de asamblare conţin prelucrări
elementare. Instrucţiunile lor sunt puse în corespondenţă cu operaţii simple.
Instrucţiunea

add O1, O2

realizează adunarea operandului O2 la operandul O1. Rezultatul este dat de


operandul O1. Instrucţiunea

mov O1, O2

efectuează copierea operandului O2. După executarea ei, operandul O1 are acelaşi
conţinut cu cel al operandului O2.
Instrucţiunea

shl O1,x

realizează multiplicarea operandului O1 cu 2x sau deplasarea spre stânga a biţilor


ce alcătuiesc conţinutul operandului O1 cu x poziţii.
Instrucţiunea

xchg O1, O2

realizează interschimbul dintre operanzii O1 şi O2. Dacă înainte de execuţie


operandul O1 este egal cu 7 iar operandul O2 este egal cu 13, după execuţia
instrucţiunii xchg, operandul O1 va fi 13, iar operandul O2 va fi 7.
407

Se identifică o multitudine de prelucrări elementare. Fiecărei prelucrări


elementare i se asociază o instrucţiune. Proiectarea limbajului de asamblare ia în
considerare rezolvarea oricărei probleme prin construirea de algoritmi la nivelul
paşilor elementari. Înseamnă că mai jos de acest nivel de detaliere nu se mai poate
coborî.
Este foarte important de observat că diferenţele dintre limbajele de
asamblare sunt nesemnificative dacă sunt analizate prelucrările elementare.
Dacă se studiază listele de instrucţiuni ale limbajelor de asamblare
ASSEMBLER, ASSIRIS, MACRO-11, corespunzătoare unor generaţii mai vechi,
cu cele ale limbajelor de asamblare definite pentru microprocesoarele de azi se
observă existenţa aceluiaşi nivel de detaliere în cazul aritmeticii binare şi în aria
instrucţiunilor destinate controlului execuţiei.

Tabelul 2.1.
Limbajul Limbaj asamblare
microprocesor
ASSEMBLER ASSIRIS MACRO-11
486 PENTIUM II
Instrucţiunea
Adunare add ad4, add add add add
Scădere sub sb4, sbd sub sub sub
Înmulţire mul mp4, mpd mul mul mul
Împărţire div dv4, dvd div div div
Comparare cmp cp4, cp1 cmp cmp cmp
Salt jmp bru br jmp jmp
necondiţionat
Salt condiţionat jz, je, jne,... bcf, bz, ... beq, bne,... jz, je, jne,... jz, je, jne,...

2.2 Caracterul neimplicit al definirilor şi prelucrărilor

Limbajele de programare evoluate posedă implementări la nivel de


compilatoare care realizează:
 alocări automate de memorie pentru operanzii utilizaţi dar nedefiniţi în
program;
 iniţializări la definire a operanzilor;
 semnalarea unor neconcordanţe între natura operanzilor sau
incompatibilităţi, cu efectuarea corecţiilor celor mai probabile sau prin
eliminarea instrucţiunii din secvenţă;
 conversiile necesare pentru a asigura gradul de omogenitate necesar al
prelucrărilor.
Limbajele de asamblare presupun existente la dispoziţia programatorului a
tuturor resurselor sistemului de calcul. Înseamnă deci, că programatorul este
obligat să:
 definească în program toţi operanzii;
 iniţializeze toate zonele de memorie;
408

 asigure omogenitatea operanzilor în vederea efectuării corecte a


operaţiilor de calcul.
În programele scrise în limbaj de asamblare se execută exact operaţiile
pentru care există instrucţiuni.
Dacă un operand nu este definit dar este utilizat, asamblorul semnalează
acest eveniment ca fiind major şi nu se va proceda la trecerea la etapele ulterioare
asamblării.
Dacă un operand nu a fost iniţializat se va lucra cu conţinutul existent al
zonei de memorie. Simplitatea programului asamblor nu permite analiza
corelaţiilor dintre instrucţiuni aşa fel încât să poată semnala că se folosesc variabile
neiniţializate.
În cazul în care un operand A este iniţializat cu un întreg binar, iar
operandul B este iniţializat cu un număr zecimal împachetat, dacă se scrie
instrucţiunea:

add a, b

cei doi operanzi sunt trataţi ca întregi binari. Dacă programatorul nu a inclus în
program procedura de realizare a conversiei operandului B această operaţie nu este
implicită, deci nu se efectuează.
Într-un limbaj algoritmic, dacă operandul de tip întreg A este iniţializat cu
27, iar operandul B de tip zecimal împachetat este iniţializat cu 13, instrucţiunea:

C=A+B

se efectuează prin parcurgerea paşilor următori:


 se converteşte operandul B de la zecimal împachetat la întreg binar

00 13  00 0D
B Temp B

Figura 2.1 – Rezultatul conversiei de la zecimal împachetat la binar

 se adună operanzii A şi Temp B şi rezultatul este memorat în variabila


C

00 1B 00 0D
A + Temp B

00 28
C
409

Figura 2.2 – Rezultatul adunării

Dacă în programul scris în limbaj de asamblare sunt definiţi operanzii A şi


B în secvenţa:

A dw 27
B dw 0013h
C dw ?

executarea adunării fără ca în prealabil să se apeleze de către programator


procedura de conversie de la reprezentarea zecimal împachetat la reprezentarea
binară,

mov ax, A
add ax, B
mov C, ax

conduce la obţinerea în operandul C a şirului 002Eh ce corespunde valorii întregi


46.

2.3 Secvenţialitatea instrucţiunilor

În programele scrise în limbaj de asamblare se află instrucţiuni dispuse una


după cealaltă, care formează secvenţe lineare. Apariţiile instrucţiunilor de salt
întrerup caracterul liniar al programelor. Chiar dacă nu sunt dispuse unele după
altele, instrucţiunile tot secvenţial se execută, figura 2.3.

I1
I2
JMP A
B: I3
I4
A: I5
I6
I7
JZ B
I8

Figura 2.3 – Secvenţă de instrucţiuni

Instrucţiunile se execută în ordinea: I1 I2 I5 I6 I7 I3 I4 I5 I6 I7 I3 … I7 I8.


În cazul utilizării instrucţiunilor de apel pentru proceduri, secvenţialitatea
este construită dinamic.
Programului din figura 2.4 îi corespunde secvenţa:
410

I1 I2 A1 A2 A3 I3 I4 B1 B2 I5 I6.

I1
I2
call A
I3
I4
call B
I5
I6
mov ax,4C00h
int 21h

A proc
A1
A2
A3
Ret
endp
B proc
B1
B2
ret
endp

Figura 2.4 – Apel de proceduri

Programatorul cunoaşte exact că secvenţa liniarizată conţine instrucţiuni de


salt necondiţionat, tocmai elementele care disting liniarizarea aşa cum e definită în
programarea structurată.
Programului din figura 2.4 îi corespunde în realitate secvenţa:

I1 I2 J A1 A2 A3 J I3 I4 J B1 B2 J I5 I6

unde prin J s-au notat instrucţiunile de salt necondiţionat ce corespund apelului de


proceduri, respectiv, reîntoarcerii în programul apelator.

2.4 Absenţa priorităţilor

Limbajele evoluate de programare au definite liste de operatori cu


priorităţile din matematică sau priorităţi definite special.
La construirea de expresii, evaluarea se efectuează în raport cu priorităţile.
Astfel, expresia:

a--*++b-c
411

se evaluează astfel:
 se incrementează b;
 se înmulţeşte a cu b;
 se scade c;
 se decrementează a.
Absenţa priorităţilor operatorilor în limbajele de asamblare determină ca
ordinea de efectuare a operaţiilor să fie cea indicată de succesiunea instrucţiunilor
în program. Astfel, dacă se consideră expresia:

a*b+c*d

şi se construieşte secvenţa:

mov ax,a
mul b
add c
mul d

se obţine de fapt evaluarea expresiei:

((a*b)+c)*d

Pentru evaluarea corectă, cu luarea în considerare a ordinei de efectuare a


operaţiilor aritmetice se scrie secvenţa:

mov ax,a
mul b
mov e,ax
mov ax,c
mul d
add e,ax

ceea ce corespunde evaluării:


e=a*b
e=e+c*d

sau:

(e=(e=(a*b))+(c*d))

Limbajul de asamblare dă posibilitatea programatorului să recompună


expresii complexe prin traversarea secvenţială a instrucţiunilor elementare.
412

2.5 Contextul dinamic

Limbajele de programare sunt structurate în definiri de tipuri de operanzi şi


referiri de operanzi în vederea prelucrării.
Contextul de prelucrare începe cu definirea operanzilor. Între modul de
definire şi modul de utilizare există restricţii pe care programatorul trebuie să le
respecte.
În definirea limbajelor evoluate sau la implementările unor compilatoare
sunt specificate restricţiile şi libertăţile care sunt în legătură cu o serie de operaţii.
Sunt numeroase tabelele în care apar tipuri de operanzi şi se indică prin Y (Yes)
sau N (No) dacă o anumită operaţie sau conversie este posibilă (implementată) sau
nu.
Astfel, pentru instrucţiunea MOVE op1 to op2 din limbajul COBOL se
consideră tipurile de date şi permitivităţile din tabelul
Tabelul 2.2.
Op2
Zecimal Zecimal Şir
Întreg Display
împachetat despachetat caractere
Op1
Întreg Y Y Y Y Y
Zecimal
Y Y Y Y Y
împachetat
Zecimal
Y Y Y Y Y
despachetat
Display Y Y Y Y Y
Şir
N N N N Z
caractere

Limbajele de asamblare definesc tipuri de zone de memorie ca lungime (DB


– define byte, DW – define word, DD – define double etc. ) şi ca aliniere, fără a le
pune în corespondenţă cu tipurile constantelor (integer, float, char, pointer,
boolean, complex, decimal, display, computational, real, double, string, etc.).
Sensul unui operand este dat de locul pe care îl are în lista de operanzi şi de
instrucţiuni care îl foloseşte.
Se consideră definirea:

a dw 80

În instrucţiunea

add a, 2
413

operandul a este termen al unei adunări şi rezultat al acesteia.


În instrucţiunea:

add ax,[a]

operandul a este o variabilă pointer care indică adresa unde se află operandul care
participă ca termen la efectuarea adunării.
În instrucţiunea

cmp a+1,’c’

se preia unul dintre baiţi şi este interpretat caracter pentru a fi comparat cu


caracterul ‘c’.
Semnificaţiile operandului a sunt date de contextul creat de programator
prin utilizarea unei anumite instrucţiuni şi prin poziţionarea anumită a respectivului
operand în expresia de adresare. Cunoaşterea foarte exactă a tipurilor de expresii de
referire şi a instrucţiunilor permit găsirea de soluţii elegante de a obţine prelucrări
foarte diferite şi de a da sensuri noi limbajului însuşi.

2.6 Libera poziţionare

Orice program foloseşte operanzi şi operatori. Limbajul de asamblare


permite structurarea distinctă pe segmente a datelor, a stivei şi a codului executabil.
Mai mult, există posibilitatea de a defini o variabilă oriunde în segmente şi de a o
referi cu semnificaţia pe care contextul local (cod instrucţiune, poziţie în lista de
operanzi) îl dă.
De exemplu, pentru evaluarea expresiei z=x+y variabilele x,y,z se definesc
în segmentul de date:

.data
x dw 3
y dw 5
z dw ?
.stack
dw 1000 dup (?)
.code
...
mov ax,x
add ax,y
mov z,dx

Aceeaşi soluţie se obţine şi dacă variabilele se definesc pe stivă:

.data
.stack
414
x dw 3
y dw 5
z dw ?
.code
...
mov bx, bp
mov ax, [bx]
add ax, [bx+2]
mov [bx+4], ax

Dacă variabilele x, y, z se definesc în segmentul de cod al programului se


obţine construcţia:

.code
mov ax,@code
mov ds,ax
jmp alfa
x dw 3
y dw 5
z dw ?
alfa:
mov ax,a
add ax,y
mov z,ax

Mai mult, poziţionarea operanzilor se efectuează fie cu luarea în


considerare a cerinţelor de aliniere, fie fără a se ţine seama de cerinţele de aliniere
se procedează astfel:
 se definesc mai întâi variabilele de tip dd;
 se definesc mai apoi variabilele de tip dw, dt;
 ultimele se definesc variabilele de tip db sau de tip struc.
În cazul în care nu este respectată această regulă şi variabilele de tip dw, dd,
dt se află la adrese impare, pentru referirea operanzilor sunt necesare cicluri maşină
în plus la execuţie.

2.7. Adresarea generalizată

În limbajele de asamblare sunt definite modalităţi variate de referire a


operanzilor (adresare directă, adresare indirectă, adresare indexată, adresare bazată,
etc.). Baiţii sunt puşi în corespondenţă cu identificatori şi cu etichete.
Adresarea generalizată se referă la faptul că în program sunt adresabili toţi
baiţii, indiferent de locul pe care îl au în segmentul de stivă, în segmentul de date
sau în segmentul de cod. Semnificaţia zonei de memorie referite este dată de
context (codul instrucţiunii şi poziţia în lista de operanzi).
Adresarea generalizată include şi posibilitatea de a referi oricare bait din
orice punct al segmentului din care baitul face parte.
415

În definirea:

.data
a dw 300 dup (?)
b dw 100 dup (?)
...
mov ax,a+200
mov bx,b-400

se referă acelaşi element din masivul unidimensional, A[100] ( figura 2.5.).

A[0] A[1] A[100] A[299] B[0] B[1]

a a+2 a+200 a+598 b b+2

b-400

mov ax, a+200 mov bx, b-400

Figura 2.5 – Referiri element în raport cu bazele a şi b

Tratarea diferenţiată a identificatorilor şi a etichetelor din programele scrise


în limbaje algoritmice exclud posibilitatea modificării textului executabil în timpul
execuţiei programului.
Dacă într-un program C++, alfa este eticheta, instrucţiunea

alfa++

este o construcţie incorectă, semnalată în faza de compilare.


Într-un program scris în limbaj de asamblare, considerând secvenţa:
mov ax,5
b: mov ax,5
a: inc ax

atunci instrucţiunile:

inc b
sau

add b, 01h

în cazul în care ar fi acceptate de asamblor, ar putea fi folosite în transformarea


dinamică a codului operaţiei.
416

Se observă absenţa restricţiilor în referirea de baiţi şi modificarea acestora.


Instrucţiunea

mov ax,7

se structurează astfel:

b b+1 b+2
00 07

cod operaţie
Figura 2.6 – Structura instrucţiunii mov ax,7

deşi la asamblare pe baitul al treilea al instrucţiunii cu eticheta b se află numărul 7


instrucţiunea:
mov b+2,15

modifică valoarea conţinută de acest bait, iar rezultatul adunării este 25 (10+15) şi
nu 17 (10+7).

2.8 Necompactitatea construcţiilor

Întrucât o instrucţiune efectuează o operaţie simplă şi instrucţiunile se


organizează în secvenţe programul scris în limbaj de asamblare este văzut ca
secvenţe şi nu ca expresii.
Mai mult, limitele de poziţionare a instrucţiunilor în secvenţe reduc gradul
de compactare a programelor.
Dacă în limbajul C++ expresia de atribuire multiplă sau expresia virgulă
permit creşterea gradului de agregare cum o efectuează şi operatorul condiţional, în
programele scrise în limbaj de asamblare acest grad de agregare nu este atins
datorită simplităţii operaţiilor cu care sunt asociate instrucţiunile.
Evaluarea expresiei:

m = min(a,b,c)

se realizează prin secvenţele de program scris în limbajul C.

m=a
if (mb) m=b;
if (mc) m=c;

Compactitatea secvenţei creşte prin utilizarea operatorului condiţional:


417

(ab) ? (bc) ? m=c: m=b:


(ac) ? m=c: m=a;

se evaluează o singură expresie cu grad de complexitate ridicat.


În programul scris în limbaj de asamblare evaluarea expresiei revine la
utilizarea secvenţei.

...
mov ax,a
cmp ax,b
jle e1
mov ax,b
e1: cmp ax,c
jle e2
mov ax,c
e2 mov m,ax
...

2.9 Incluziunea

Până la apariţia limbajelor de programare din anii ’70, limbajelor de


asamblare li se atribuiau o serie de caracteristici, dintre care se enumeră:
 accesul la toate resursele sistemelor de calcul (registre, funcţii de bază,
memorie la nivel de bit);
 posibilitatea de a efectua prelucrări imposibil de efectuat în limbaje
precum COBOL, FORTRAN din cauza modurilor de adresare şi a
expresiilor de referire;
 definirea de mecanisme specifice, particulare de referire a operanzilor şi
operatorilor.
Odată cu apariţia limbajelor C++, Pascal aceste bariere au dispărut. Toate
prelucrările specifice limbajelor de asamblare sunt incluse în limbajele C++ şi
Pascal.
Instrucţiunilor inc şi dec le corespund operatorii ++, respectiv --. Mai mult,
în limbajul C++ apar nuanţările de preincrementare, postincrementare,
predecrementare, postdecrementare.
Accesul la registre se efectuează cu structura:

struct REGPACK
{
unsigned r_ax, r_bx, r_cx, r_dx;
unsigned r_bp, r_si, r_di;
unsigned r_ds, r_es, r_flags;
};

Figura 2.7 – Structura folosită pentru accesul la regiştri


418

Accesul la funcţiile sistemului de operare se efectuează direct, folosind


funcţii cu acelaşi cod pe care îl au întreruperile sistemului de operare. De exemplu
secvenţa de schimbare a directorului curent cu ajutorul funcţiilor sistemului de
operare:

int main(void)
{
char directory[80];
struct REGPACK reg;

printf("Introduceţi numele directorul de schimbat:");


gets(directory);
reg.r_ax = 0x3B << 8; /*incarcă AH cu 3Bh*/
reg.r_dx = FP_OFF(directory);
reg.r_ds = FP_SEG(directory);
intr(0x21, &reg);
if (reg.r_flags & CF)
printf("Schimbare director esşuată\n");
getcwd(directory, 80);
printf("Directorul curent este: %s\n", directory);
return 0;
}

Mai mult, în programele C++ se scriu secvenţe de instrucţiuni scrise în


limbaj de asamblare care dau posibilitatea programatorului să controleze mai bine
utilizarea resurselor fizice din programul său.
#include <stdio.h>

void main()
{
unsigned char a=10, b=11, c;
asm {
mov al,a
add al,b
mov c,al
}
printf("\nc=%d\n",c);
}

Figura 2.8 – Folosirea limbajului de asamblare într-un program scris în C++

2.10 Concluzii

Aceste caracteristici au rolul de a diferenţia limbajele de asamblare de


celelalte limbaje. Va fi mai clar momentul în care programatorul trebuie să
419

dezvolte secvenţe sau proceduri în limbaj de asamblare pentru a obţine performanţa


în aplicaţiile sale complexe.

3
REPREZENTAREA INFORMAŢIEI
Chiar dacă un computer stochează diverse tipuri de informaţie (texte,
imagini, sunete), această informaţie este reprezentată intern ca informaţie numerică.
Aceasta face ca o importanţă deosebită să fie acordată reprezentării şi prelucrării
acestui tip de informaţie.

3.1 Codificarea informaţiei

Sistemele de calcul folosesc pentru prelucrarea şi transmiterea


informaţiei două nivele de tensiune (de obicei 0V pentru 0 logic si +3.3V până la
+5V pentru 1 logic). Cu aceste două nivele de tensiune se reprezintă cele două
valori diferite, notate prin convenţie cu ‘0’ şi ‘1’, şi care corespund cifrelor
sistemului de numeraţie binar.

Tabelul 3.1.
Binar Octal Hexazecimal Zecimal
0000 0 0 0
0001 1 1 1
0010 2 2 2
0011 3 3 3
0100 4 4 4
0101 5 5 5
0110 6 6 6
0111 7 7 7
1000 10 8 8
1001 11 9 9
1010 12 A 10
1011 13 B 11
1100 14 C 12
1101 15 D 13
420

1110 16 E 14
1111 17 F 15

Pe lângă sistemul de numeraţie binar care utilizează baza de


numeraţie 2, în sistemele de calcul sunt folosite şi sistemele de numeraţie octal,
hexazecimal şi bineînţeles zecimal, care folosesc bazele de numeraţie 8, 16
respectiv 10. În sistemul de numeraţie hexazecimal, numerele de la 10 la 15 sunt
codificate cu ajutorul literelor: ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’. În tabelul 3.1 este
prezentată corespondenţa pentru cele patru sisteme de numeraţie.
Toate aceste sisteme de numeraţie sunt numite şi sisteme de numeraţie
poziţionale, aceasta însemnând că ‘valoarea’ fiecărei cifre depinde, la formarea
numerelor, de poziţia acesteia în cadrul numărului.
Pentru a exemplifica această afirmaţie considerăm numărul 42349(10) scris
în baza 10 (vom folosi în continuare notaţia ‘(b)‘ pentru a indica baza ‘b‘ în care a
fost scris numărul). Valoarea acestui număr se calculează astfel:

410.000 + 21.000 + 3100 + 410 + 91 = 42349(10)

Se observă că cifra 4 participă la formarea numărului de două ori, prima dată având
‘valoarea’ 40.000, iar a doua oară având ‘valoarea’ 40.
O altă observaţie foarte importantă este aceea că ‘valorile’ cifrelor se obţin
înmulţindu-le cu puteri ale bazei (1=100, 10=101, 100=102, 1.000=103, etc.).
Aceasta afirmaţie este valabilă şi în cazul altor sisteme de numeraţie poziţionale,
printre care şi sistemele binar, octal şi hexazecimal. Numărul 10010(2) reprezintă:

124 + 023 + 022 + 121 + 020 = 18(10).

3.2 Organizarea datelor

O singură cifră binară (‘0’ sau ‘1’) este numită ‘bit’ (prescurtare de la
BInary digiT) atunci când este implementată hardware într-un computer. Deoarece
în memoria computerului informaţia este reprezentată ca o înşiruire de biţi, (‘0’ şi
‘1’), se pune problema localizării unei anumite părţi a informaţiei (de exemplu un
număr care participă la o operaţie de adunare). Pentru a facilita accesul la
informaţie, biţii au fost grupaţi prin convenţie. Modalităţile uzuale de grupare
folosite sunt: un singur bit, câte patru biţi (numiţi ‘nibble’), câte opt biţi (numiţi
‘bait’ sau 'octet'), câte 16 biţi (numiţi ‘cuvânt’), câte 32 biţi (numiţi ‘dublu-
cuvânt’).

3.2.1 Bit
421

Deoarece un singur bit este capabil să reprezinte doar două valori diferite,
există impresia că foarte puţină informaţie poate fi reprezentată cu ajutorul unui
bit. Dar nu este chiar aşa! Se poate reprezenta cu ajutorul unui bit informaţie
privitoare la valori de adevăr (Adevărat sau Fals), la stările unui comutator (Închis
sau Deschis), la sexul unei persoane (Bărbătesc sau Femeiesc), etc.

3.2.2 Nibble

Construcţia nibble este un grup de patru biţi. El prezintă interes numai din
punctul de vedere al codificării numerelor în format BCD (Binary-Coded Decimal)
şi al reprezentării numerelor hexazecimale. Cu ajutorul unui nibble se reprezintă cel
mult 16 valori distincte.
Structura internă a unui nibble este:

b3 b2 b1 b0

Figura 3.1 – Structura internă a unui nibble

unde cu b0,b1,b2,b3 au fost notaţi cei patru biţi care îl compun.

3.2.3 Bait

Baitul este cea mai importantă structură de date folosită într-un computer.
Are o lungime de opt biţi, notaţi b0, b1, b2, b3, b4, b5, b6, b7, cel mai semnificativ
fiind bitul b7, iar cel mai puţin semnificativ b0.

b7 b6 b5 b4 b3 b2 b1 b0

Figura 3.2 – Structura internă a unui bait

Un bait conţine exact doi nibble, ceea ce înseamnă că se reprezintă cu


ajutorul unui bait două cifre hexazecimale. Numărul de valori distincte ce pot fi
reprezentate pe un byte, este de 256 (28). În computer un bait este folosit pentru a
reprezenta:
- numere fără semn de la 0 la 255;
- numere cu semn de la -128 la +127;
- codurile caracterelor ASCII;
- alte tipuri de date pentru care 256 simboluri distincte sunt suficiente.

3.2.4 Cuvânt
422

Limitele pentru cuvânt au fost stabilite prin convenţie la 16 biţi sau la


dimensiunea magistralei de date a procesorului. Aceasta înseamnă că dimensiunea
unui cuvânt este variabilă, depinzând de procesorul instalat pe sistem. În continuare
vom folosi însă cuvântul definit doi baiţi (16 biţi).
Structura unui cuvânt este:

b15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0

Figura 3.3 – Structura internă a unui cuvânt

Un cuvânt este împărţit în doi baiţi sau patru nibble. Cei doi baiţi din
componenţa unui cuvânt poartă numele de baitul cel mai semnificativ (MSB –
most significant byte) şi baitul cel mai puţin semnificativ (LSB – less significant
byte) format din biţii b7-b0.
Cu ajutorul unui cuvânt sunt reprezentate 216=65536 valori distincte. Cele
mai importante utilizări ale unui cuvânt sunt pentru reprezentarea:
 întregilor pe 16 biţi;
 adreselor pe 16 biţi;
 numerelor ce au nevoie de cel mult 16 biţi;
 instrucţiunilor cu operanzi impliciţi.

3.2.5 Dublu-cuvânt

Aşa cum arată şi numele un dublu cuvânt este format prin concatenarea a
două cuvinte. Lungimea unui dublu-cuvânt este de 32 biţi şi stochează
4,294,967,296 valori distincte.

3.3 Reprezentarea informaţiei numerice

Informaţia numerică este organizată într-un computer în diferite moduri, în


funcţie de scopul memorării acesteia şi de modurile de prelucrare. Din punct de
vedere al existenţei părţii zecimale, numerele sunt întregi sau reale.

3.3.1 Reprezentarea numerelor întregi în formatul binar

Numerele întregi se reprezintă pe unul sau mai mulţi baiţi. Se notează acest
număr cu n. De obicei numărul n are valoarea 1, 2, sau 4. Există două cazuri
distincte de reprezentări:
- pentru numere întregi fără semn;
- pentru numere cu semn.
423

Pentru numerele întregi fără semn, reprezentarea acestora într-un sistem de


calcul se face identic cu reprezentarea acestora în sistemul de numeraţie binar.
Pentru aceste numere, valoarea maximă reprezentabilă pe numărul dat de baiţi, este
2n8-1 unde 8 este numărul de biţi ai unui bait (‘-1’ apare datorită necesitaţii de a
reprezenta numărul ‘0’). În tabelul următor sunt prezentate câteva numere şi
reprezentările lor ca numere întregi fără semn pe doi baiţi.

Tabelul 3.2.
Valoare Reprezentare (n=2)
0 0000 0000 0000 0000
1 0000 0000 0000 0001
2 0000 0000 0000 0010
3 0000 0000 0000 0011
4 0000 0000 0000 0100
64 0000 0000 0100 0000
128 0000 0000 1000 0000
256 0000 0001 0000 0000
1024 0000 0100 0000 0000

Reprezentarea numerelor întregi cu semn se face cu ajutorul unui sistem de


reprezentare numit complement faţă de doi, primul bit (cel mai semnificativ) fiind
considerat prin convenţie bit de semn. Dacă acest bit are valoarea ‘1’, atunci
numărul are o valoare negativă, iar dacă are valoarea ‘0’, numărul este pozitiv.
Valoarea absolută a numărului se calculează diferit după cum valoarea numărului
numărul este pozitivă sau negativă:
 dacă valoarea numărului este pozitivă (primul bit are valoarea ‘0’),
atunci restul de n8-1 biţi reprezintă chiar valoarea numărului;
 dacă valoarea numărului este negativă (primul bit are valoarea ‘1’),
atunci valoarea absolută a numărului se obţine prin complement faţă de
2.
Pentru a obţine complementul faţă de 2 al unui număr se inversează toţi biţii
numărului (‘0’ trece în ‘1’ şi invers) şi apoi se adună la rezultat valoarea ‘1’. Acest
procedeu se aplică şi în cazul transformării inverse (din număr pozitiv în număr
negativ).
De exemplu pentru a găsi reprezentarea numărului –5 pe un bait, se va
proceda astfel:
 se scrie numărul în sistemul binar: 0000 0101
 se inversează toate cifrele numărului: 1111 1010
 se adună valoarea ‘1’ la rezultat: 1111 1011
424

În continuare este prezentată transformarea inversă, pentru a obţine numărul


reprezentat prin configuraţia de biţi 1111 1011 (se cunoaşte că numărul este
reprezentat cu semn, pe un bait):
 primul bit este ‘1’ deci numărul este negativ; se aplică procedeul de
complementare faţă de 2 pentru a obţine valoarea absolută a numărului;
 biţii _111 1011 se inversează: _000 0100 (nu s-a ţinut cont de bitul de
semn);
 se adună ‘1’ la rezultat: _000 0100 + 1 = _000 0101, adică
reprezentarea numărului 5 în sistemul binar.
Numerele ce se reprezintă pe n baiţi în acest format au domeniul de valori
cuprins între –2n8-1…2n8-1-1. În tabelul următor se găsesc câteva numere întregi
negative reprezentate pe doi baiţi:

Tabelul 3.3.
Valoare Reprezentare (n=2)
0 0000 0000 0000 0000
1 0000 0000 0000 0001
2 0000 0000 0000 0010
3 0000 0000 0000 0011
-1 1111 1111 1111 1111
-2 1111 1111 1111 1110
-3 1111 1111 1111 1101
-4 1111 1111 1111 1100
-5 1111 1111 1111 1011

3.3.2 Reprezentarea numerelor întregi în formatul BCD

Formatul BCD(binary-coded decimal) este folosit pentru a reprezenta


numere zecimale întregi, fiecare cifră fiind reprezentată pe patru biţi (un nibble).
De exemplu, numărul 375 va fi reprezentat astfel:

0011 0111 0101

Un avantaj al acestei reprezentări faţă de reprezentarea binară este acela că


nu există o limită maximă a dimensiunii numărului reprezentat. Pentru a mai
adăuga o cifră numărului se adaugă încă patru biţi, în timp ce numerele
reprezentate în format binar sunt limitate de 8, 16, 32 sau 64 de biţi.
În computer, pentru stocarea acestor numere sunt folosite două reprezentări
diferite:
 reprezentarea zecimală împachetată (packed BCD);
 reprezentarea zecimală despachetată (unpacked BCD).
425

3.3.2.1 Reprezentarea zecimala despachetată se concretizează prin


ocuparea de către fiecare cifră a numărului a câte unui bait. Astfel, numărul 75431
se va reprezenta în format zecimal despachetat pe 5 baiţi, astfel:

00 07 00 05 00 04 00 03 00 01

un bait
Figura 3.4 – Reprezentarea zecimală despachetată

Dacă numerele se introduc de la tastatură ca şir de caractere ASCII, cifrele


numărului reprezentat zecimal despachetat se obţin prin scăderea numărului 30h
din codurile caracterelor.

Tabelul 3.4.
Caracter Codul hexazecimal ASCII
0 30
1 31
2 32
3 33
4 34
5 35
6 36
7 37
8 38
9 39

3.3.2.1 Reprezentarea zecimala împachetată presupune ocuparea fiecărui


bait de câte două cifre zecimale. Astfel numărul 75431 se va reprezenta în format
zecimal împachetat astfel:

00 07 05 04 03 01

un bait
Figura 3.5 – Reprezentarea zecimală împachetată

3.3.3 Reprezentarea numerelor reale

Reprezentarea numerelor reale se face în două moduri distincte, şi anume


reprezentare în virgulă fixă şi reprezentare în virgulă mobilă, în funcţie de modul
de reprezentare a părţii întregi şi a părţii fracţionare a numărului.
426

3.3.3.1 Reprezentarea numerelor reale în virgulă fixă presupune că se


alocă pentru reprezentarea părţii întregi şi a părţii fracţionare a numărului, un
număr de cifre constant. Dacă se consideră, de exemplu, că un număr în baza 10
reprezentat va avea o precizie de 0.001 şi că acest număr nu poate fi mai mare
decât 9999, atunci numărul de cifre pentru partea întreagă este 4, iar cel pentru
partea fracţionară este 3. Dacă numărul are şi valori negative, atunci se impune
folosirea şi a unui câmp separat pentru reprezentarea semnului, care prin convenţie
va avea valoarea 0 dacă numărul este pozitiv sau valoarea 1 dacă numărul este
negativ. Deoarece sunt cunoscute numărul de cifre al părţii întregi şi numărul de
cifre al părţii fracţionare, necesitatea reprezentării virgulei dispare. Fiecare cifră se
va reprezenta fie pe un octet (din care vor fi ocupaţi doar patru biţi), fie câte două
pe un octet. Câteva numere astfel reprezentate sunt:

Tabelul 3.5.
Reprezentare
Număr
semn mii sute zeci unităţi zecimi Sutimi Miimi
4823.493 0 4 8 2 3 4 9 3
-52.3 1 0 0 5 2 3 0 0
2.445 0 0 0 0 2 4 4 5

3.3.3.2 Reprezentarea numerelor reale în virgulă mobilă (floating


point) se foloseşte în reprezentarea numerelor implicate în calcule ştiinţifice.
Forma generală a unui număr reprezentat în virgulă mobilă este:
(-1)SMBE
unde:
 S este un bit folosit la reprezentarea semnului numărului;
 M este un număr pozitiv subunitar, numit mantisă;
 B este baza de numeraţie în care se face reprezentarea numărului;
 E este un număr întreg cu semn, numit exponent.
Pentru a elimina problema reprezentării semnului exponentului, se adună la
valoarea acestuia o constantă C pentru a deplasa astfel intervalul în care exponentul
poate lua valori. Dacă exponentul se reprezintă pe 8 biţi, iar un bit va fi folosit
pentru semn, exponentul va lua valori în intervalul [–128, 127]. Pentru a deplasa
acest interval, se adună la valoarea exponentului constanta C = 128, intervalul în
care va putea lua valori exponentul după această transformare devenind [0, 255].
Deoarece cel mai mic număr din interval este acum pozitiv, semnul nu se va mai
reprezenta, cei 8 biţi fiind suficienţi în continuare pentru reprezentarea
exponentului deplasat.
La reprezentarea numerelor în virgulă mobilă se aplică o transformare şi
asupra mantisei, numită normalizare, în urma căreia, prima cifră de după virgulă
va fi diferită de 0 (ceea ce impune şi modificarea exponentului). Dacă
427

reprezentarea numărului se face în baza 2, atunci prima cifră de după virgulă va fi


întotdeauna 1 şi nu va fi reprezentată nici aceasta.
Trei standarde privind reprezentările numerelor în virgulă mobilă sunt
folosite în funcţie de precizia dorită. Cele trei standarde diferă din punctul de
vedere al numărului de cifre disponibile reprezentărilor părţii întregi şi a părţii
fracţionare. Aceste standarde sunt prezentate în următoarele figuri:

B7 B6 B5 B4 B3 B2 B1 B0
B15 B14 B13 B12 B11 B10 B9 B8
E0 B22 B21 B20 B19 B18 B17 B16
S E7 E6 E5 E4 E3 E2 E1

Figura 3.6 – Reprezentarea în virgulă mobilă, simplă precizie

B7 B6 B5 B4 B3 B2 B1 B0
B15 B14 B13 B12 B11 B10 B9 B8
B23 B22 B21 B20 B19 B18 B17 B16
B31 B30 B29 B28 B27 B26 B25 B24
B39 B38 B37 B36 B35 B34 B33 B32
B47 B46 B45 B44 B43 B42 B41 B40
E3 E2 E1 E0 B15 B50 B49 B48
S E10 E9 E8 E7 E6 E5 E4

Figura 3.7 – Reprezentarea în virgulă mobilă, dublă precizie

B7 B6 B5 B4 B3 B2 B1 B0
B15 B14 B13 B12 B11 B10 B9 B8
B23 B22 B21 B20 B19 B18 B17 B16
B31 B30 B29 B28 B27 B26 B25 B24
B39 B38 B37 B36 B35 B34 B33 B32
B47 B46 B45 B44 B43 B42 B41 B40
B55 B54 B53 B52 B51 B50 B49 B48
1 B62 B61 B60 B59 B58 B57 B56
E7 E6 E5 E4 E3 E2 E1 E0
S E14 E13 E12 E11 E10 E9 E8

Figura 3.8 – Reprezentarea în virgulă mobilă, precizie extinsă

S-au notat cu Bi biţii mantisei normalizate, cu Ei biţii exponentului deplasat


şi cu S semnul mantisei. Se observă aşezarea baiţilor în memorie, primul fiind
baitul cel mai puţin semnificativ al mantisei.
428

3.4 Reprezentări ale şirurilor de caractere

3.4.1 Definire şir de caractere

Conţinutul unei zone de memorie este interpretat în funcţie de context. Cei


opt biţi care alcătuiesc un bait sunt puşi în corespondenţă într-un context dat cu un
element al alfabetului ASCII .
Şirurile de constante se definesc şi se iniţializează prin construcţii de forma:

sir1 db ‘Ionescu Gheorghe’


sir2 db ‘c’, ‘i’, ‘m’, ‘e’, ‘n’, ‘t’
sir3 db 30 dup (‘=’)

Utilizarea şirurilor în programe pentru operaţii de intrare / ieşire presupune


luarea în considerare a unor caractere de control:
 delimitatorul de sfârşit de şir; în limbajul C++ delimitatorul de sfârşit de
şir este ‘\0’ iar funcţiile sistemului de operare impun ca delimitator
caracterul ‘$’.
 caracterul de control pentru trecerea la pagină nouă sau rând nou; pentru
sfârşit de rând (trecere pe rând nou) sunt folosite împreună două
caractere: ‘\13’, ‘\10’. Primul (‘\13’) numit CR (carriage return) este
folosit pentru întoarcerea la începutul rândului iar al doilea (‘\10’) numit
LF (line feed) este folosit pentru avansul la rândul următor.
 deplasarea cu o poziţie înapoi însoţită de ştergerea caracterului de pe
acea poziţie se face cu ajutorul caracterului ‘\8’ (backspace).
 deplasarea înainte cu opt poziţii se realizează cu ajutorul caracterului
TAB (‘\9’).

3.4.2 Constante de tip şir de caractere

Constantele şir de caractere sunt definite cu ajutorul apostrofurilor. Astfel:

‘abcdEFG’
‘12345678’
‘+-.a()’

sunt şiruri de caractere.


429

Lungimea unui şir de caractere este dată de numărul simbolurilor cuprinse


între apostrofuri. Astfel, şirul ‘a+b+c+-d’ are lungimea 8 (caractere), iar şirul ‘x’
are lungimea 1.
Cu ajutorul şirurilor de caractere se iniţializează numeroase câmpuri ale
structurilor de date care descriu nume de persoane, denumiri de materiale, adrese
de instituţii, numere de telefon (‘Ionescu Gheorghe’, ‘ciment’, ‘+40.1.15.15.00’ sau
‘(40)115.15.15’).

3.4.3 Variabile de tip şir de caractere

Sunt limbaje de programare care lucrează cu tipul de date şir de caractere


(string). Alte limbaje însă lucrează cu zone de memorie date prin adresă de început
(sau de sfârşit) şi prin lungime. Conţinutul baiţilor este interpretat ca reprezentând
simbolurile codului ASCII . Şi limbajul de asamblare defineşte zona de memorie ca
şir de baiţi pe care o iniţializează. Astfel, în secvenţa:

sir1 db 15 dup(?)
sir2 db ‘abcdefghijk’
sir3 db 100 dup(‘a’), ‘b’

sau alocat trei zone de memorie după cum urmează:


 prima zonă de memorie are 15 baiţi, nu este iniţializată, iar primul bait
este pus în corespondenţă cu identificatorul sir1;
 a doua zonă de memorie are atâţia baiţi câte simboluri se află între
apostrofurile ce delimitează constanta şir de caractere (11 caractere);
primul bait este pus în corespondenţă cu identificatorul sir2;
 a treia zonă a lungimea de 101 baiţi, din care primii 100 baiţi au fost
iniţializaţi cu caracterul ‘a’, iar ultimul bait a fost iniţializat cu
caracterul b; primul bait al zonei este pus în corespondenţă cu
identificatorul sir3. Expresiile de referire sir2+5, sir3+100 sau sir3-5
referă baiţii ce conţin caracterele 'f', 'b', respectiv 'g'.
430

4
MODURI DE ADRESARE

4.1 Calculul adresei unui operand

Adresa fizică a unui bait este un număr natural. În funcţie de dimensiunea


memoriei care se accesează sunt proiectate dimensiunile registrelor care stochează
informaţia de adresă.
Există calculatoare care au în structură registre pe patru baiţi numite
‘registre de bază’ care stochează adresa de început a segmentului program, de
exemplu. Adunând la această adresă deplasamentul operandului se obţine adresa lui
fizică. Numărul reprezentat pe 32 biţi este suficient de mare, şi nu mai necesită
artificii de combinare adresă segment cu deplasament.
Există tabele care concentrează corelaţia dintre lungimea expresiei de
adresă în biţi şi lungimea maximă a zonei ce poate fi accesată. Astfel, cu adrese
definite pe 20 biţi se pot accesa baiţii ce formează o memorie de 1 MB.
Algoritmul pentru calculul de adresă este format din operaţii de deplasare
spre stânga şi din adunări. Valoarea adresei apare ca o agregare a mai multor
elemente. Sunt rare cazurile când este impusă proprietatea de dezagregare unică a
adresei, în componentele iniţiale.
De cele mai multe ori, dezagregarea vizează determinarea deplasării unui
operand având ca elemente de calcul adresa fizică şi conţinutul registrului segment,
adică adresa de început a segmentului.
Procesorul 286 de exemplu, ia în considerare pentru calculul de adresă:
 conţinutul registrului segment (16 biţi);
 deplasarea operandului a cărui adresă se calculează (16 biţi).
Prin deplasarea cu patru poziţii spre stânga a conţinutului registrului
segment şi prin adăugarea la noua valoare obţinută a deplasării operandului, se
obţine adresa fizică a operandului.
De exemplu, registru segment DS are conţinutul 00F0h, iar deplasarea
operandului este 000Ah. Deplasarea spre stânga cu 4 biţi conduce la

00F00h + 000Ah=00F0Ah,

acest rezultat reprezintă adresa fizică a operandului considerat.


431

Expresia de calcul a adresei fizice a unui operand va apare de fiecare dată,


indiferent de sistemul de calcul cu care se operează sub forma unei expresii de
forma:

EA=x1*y1 + x2*y2 + … + xn*yn

unde:
xi – număr întreg, deplasare absolută sau relativă;
yi – coeficient de forma 2k, k=0,1,2,…,m;
n – numărul termenilor care intervin în calcul;
m – are valoarea 15 dacă se lucrează pe 16 biţi
respectiv, 31 dacă se lucrează pe 32 biţi.

Se consideră memoria unui calculator ca fiind formată din baiţii dispuşi


unul după altul, având adresele fizice 0000, 0001, …., FFFFh.
Memoria este împărţită în segmente de lungime fixă, lungime care se transformă în
raţia unei progresii aritmetice. Termenii progresiei sunt de fapt adresele de început
ale segmentelor. Segmentul Si va avea adresa de început a0 + (i-1)*L unde a0
reprezintă adresa primului bait al memoriei, iar L este lungimea segmentului.
În cadrul unui segment se consideră un punct fix, a cărui poziţie se
stabileşte faţă de începutul segmentului. Se consideră în continuare un alt punct a
cărui poziţie este stabilită faţă de cel considerat anterior. Poziţiile astfel obţinute
sunt poziţii relative.
Aşa cum rezultă din figura 4.1. adresa baitului x se calculează luând în
considerare deplasările relative d1, d2, d3, d4 şi deplasarea absolută d0.

d0 Sk d1 d2 d3 d4

0 A B C D x

Figura 4.1 – Calculul adresei unui operand


unde:
di – distanţe relative, iar d0 este distanţa absolută;
Sk – segmentul k din memoria calculatorului;
A – adresa de început a segmentului;
B, C, D, - baiţii consideraţi repere în segmentul Sk alese de
programatori;
x – bait oarecare ce se asociază unei variabile.

În cazul procesorului 286 adresa se calculează după formula:

EA=c0a0*22 + c1*x1 + c2*x2 + c3*x3


432

unde:
a0 – adresa de început a segmentului, conţinută într-un registru
(CS, DS, ES);
ci – coeficientul cu valoare unu dacă tipul de deplasare relativă
i este definit (c1 corespunde deplasării ca bază, c2
corespunde deplasării variabile, c3 corespunde deplasării
baitului asociat unei variabile); dacă c0=c1=c2=c3=0
adresarea este imediată;
xi – deplasarea de tip i; x1 este stocată într-un registru de bază,
x2 este stocată într-un registru index, iar deplasarea x3
este pusă în corespondenţă cu numele unei variabile.
Expresiile de calcul ale adreselor prin valorile diferite ale coeficienţilor c i
determină modurile de adresare.
Modul de adresare este definit ca algoritm de calcul al adresei absolute pe
bază de distanţe ale operanzilor.
Modul de adresare oferă posibilitatea implementării atât a structurilor
fundamentale de date (capacitatea de a referi elemente din matrice, linii din matrice
sau chiar matricea în ansamblul ei) cât şi a structurilor de programare (structura
repetitivă este posibilă numai utilizând registrul de index, iar includerea de structuri
repetitive presupune şi utilizarea unui registru de bază).

4.2 Modul de adresare imediată

Acest mod de adresare corespunde situaţiei în care programatorul scrie


instrucţiuni de forma R-I, M-I, ca de exemplu:
mov ax, 1ah
add ax, 2
xor bx, 0fa00h
mov alfa, 500

Programul asamblor generează codul asociat fiecărei instrucţiuni incluzând


lângă baitul (baiţii) de descriere ai codului operaţiei şi ai modului de adresare codul
asociat constantei definite în al doilea operand. Astfel, secvenţa:
mov cx, 4 ;b9 0004
sub si, 15 ;83 ee 0f
add ax, 100 ;05 0064
mov byte ptr[bx+1], 120 ;c6 47 01 78
mov dx, 16 ;ba 0010

pune în evidenţă faptul că asamblorul generează chiar în corpul codului asociat


fiecărei instrucţiuni constanta (al doilea operand) cu care se operează.
Din analiza numărului de cicluri maşină rezultă că adresarea imediată
necesită un număr de cicluri mai mare. Constantele se utilizează când trebuie
433

incrementat sau decrementat un registru cu o raţie mai mare ca 2, când se lucrează


cu măşti în instrucţiuni logice şi când se efectuează comparări pentru a defini limite
de intervale. Astfel, instrucţiuni ca:
mov ax,0 ;iniţializare registru
cmp bx,100 ;comparare
jl alfa ;salt dacă este la stânga intervalului
cmp bx,400 ;comparare
jg beta ;salt dacă este la dreapta intervalului
and bx,0ff0h ;’şi’ pe biţi registru bx cu masca 0ff0h
add si,4 ;incrementare registru si
sub di,2 ;decrementare registru di

demonstrează utilitatea modului de adresare imediată pentru a nu face exces de


definire de variabile în memorie şi de folosire a instrucţiunilor de tip R-M.

Instrucţiunea:

mov ax,200 ;iniţializare registru – 4 cicluri

poate fi înlocuită prin instrucţiunea:

mov ax,val_200 ;14 cicluri

unde variabila val_200 a fost definită prin:

val_200 dw 200

şi este folosită o singură dată. De regulă nu se definesc zone de memorie pentru a fi


folosite o singură dată şi mai ales pentru iniţializări. Este necesară definirea corectă
a constantelor, aşa fel încât lungimea operandului destinaţie să fie mai mare sau
egală cu lungimea operandului sursă la definire, conversia de lungime este
efectuată de programul asamblor.
În secvenţa:

mov al, 2000


mov ax, 100
mov bx, 4000

lungimea registrului al este de un bait în timp ce constanta 2000 se reprezintă pe 2


baiţi, deci construcţia este eronată.
A doua instrucţiune are operandul destinaţie cu lungime de 2 baiţi, iar
operandul sursă poate fi generat pe un bait. Programul asamblor va face conversia,
generând constanta 0064h. În a treia instrucţiune, lungimile operanzilor coincid.
Adresarea imediată se dovedeşte ineficientă atunci când constantele definite ca
434

operanzi trebuie modificate la dezvoltarea programelor în timp. Acest impediment


se elimină prin definirea de constante simbolice. Instrucţiunea:

mov ax,100

este echivalentă cu instrucţiunea:

mov ax,c_100

unde în prealabil s-a făcut definirea:

c_100 equ 100

4.3 Modul de adresare registru

Se utilizează atunci când operanzii se află în registre, fiind specific tipurilor


de instrucţiuni R-R.
La definirea mnemonicelor şi a codurilor de operaţii, când se lucrează în
modul R-R instrucţiunile au asociate coduri distincte.
Viteza de calcul este superioară, prin numărul de cicluri, de regulă mai mic
necesar pentru acest mod de adresare.
Secvenţa:

inc ax 40h
push ax 50h
pop ax 58h
xchg cx,ax 91h

pune în evidenţă faptul că registrele prefixate sunt cele care influenţează structura
codului fiecărei instrucţiuni.
Modul de adresare registru conduce la dezvoltarea în procesul de asamblare
a codurilor de instrucţiuni pe un bait (dacă registrele sunt prefixate) sau pe doi baiţi
pentru combinaţii oarecare de registre.
Unele dezvoltări de coduri sunt prezentate în secvenţa:
mov cs,ax 8e c8
mov ds,ax 8e d8
mov ss,ax 8e d0
shr ah,cl d2 ec
xor ax,ax 33 c0
sub si,cx 2b f1

Se observă că al doilea bait conţine constantele hexazecimale F, D, E, C


fapt ce determină ca dezvoltarea biţilor 7, 6 ce corespund codului mm din baitul
MODRM (vezi capitolul INSTRUCŢIUNI) să fie 11b, adică biţii 2, 1, 0 vor fi
435

interpretaţi cod registru. Modul de adresare registru este obligatoriu la iniţializarea


registrelor segment (mov reg-seg, AX).
Când se efectuează calcule şi rezultate intermediare sunt stocate în alte
registre, în final se va obţine un rezultat care va fi în registrul AX. Există operaţii
care impun lucrul cu registre, precum XCHG (Exchange), PUSH, POP, INC, însă pot
lucra şi cu operanzi definiţi în memorie, dar specificul informaţiilor pe care le
vehiculează oferă frecvenţă mai mare structurilor de tip R-R a naturii operanzilor.
Deci, în programe, acestea vor apare frecvent având operanzi registre.

4.4 Adresarea directă

Este modul de adresare cel mai frecvent întâlnit în programe. Corespunde


tipurilor de instrucţiuni R-M, M-R. Un operand este registru, iar al doilea operand
ocupă o zonă de memorie, pusă în corespondenţă cu un nume.
Adresarea directă presupune instrucţiuni de forma:
cod_operaţie operand_1, nume_variabilă
sau
cod_operaţie nume_variabilă,operand_2.
Figura următoare arată cum funcţionează o instrucţiune mov ax, x în care se
foloseşte acest mod de adresare:

mov ax, x AX
4F 13
1 2

13 4F
x
Figura 4.2 – La primul pas se face calculul adresei de memorie iar la al doilea pas
conţinutul zonei de memorie este mutat în registrul AX

Secvenţa:

mov ax, alfa


mov al, byte ptr beta
mov gama, dx
add ax, sum
sub cx, ratia
mov ah, sir+2
add sir-5, al
436

utilizează operanzii alfa, beta, gama, sum, ratia, şir, direct prin numele lor, ştiind că
în procesul de asamblare acestor nume li se asociază deplasări faţă de adresa de
început a segmentului unde sunt definite.
Dacă s-au definit şi s-au alocat zone pentru operanzii din secvenţa:

00a0 a dw 10
00a2 b dw 20
00a4 c dw 30, 40, 10, 20
00ac e dw ?

referirea lor folosind modul de adresare directă din secvenţa:

mov ax, a
add ax, b
add ax, c
add ax, c+6
mov e, ax

şi generarea la asamblare arată modul de folosire a deplasărilor 00A0, 00A2, 00A4,


00AC, care deşi puteau fi reprezentate pe un bait sunt expandate pe doi baiţi, lucrul
cu deplasări impune operanzi pe 16 biţi. În modelul de calcul al expresiei de adresă
c1=c2=0 şi c0=c3=1. Distanţa apare dintr-o singură ‘bucată’. Expresia de adresă
este constantă, includerea în structuri repetitive echivalează cu folosirea de
invarianţi, dacă se justifică.
Secvenţa:

mov ax, alfa+10*3-4


ciclu:
add ax, beta
loop ciclu

exemplifică introducerea unui invariant (beta) în structura repetitivă, justificabil


dacă se urmăreşte o înmulţire cu un număr dat a conţinutului zonei beta.

4.5 Adresarea indexată

Procesorul este înzestrat cu registrele index SI şi DI. Conţinutul unui


registru index este un număr, termen al unei progresii aritmetice, cu semnificaţie de
deplasare relativă.
Adresarea indexată permite traversarea de structuri de date omogene şi
implementarea structurilor repetitive.
Registrul index trebuie mai întâi iniţializat, după care conţinutul său se
modifică. Dacă traversarea structurii omogene se efectuează de la primul element
spre ultimul, iniţializarea registrului index se face cu valoarea zero, iar în timpul
traversării i se va adăuga o raţie. Când traversarea se efectuează de la ultimul spre
437

primul element, conţinutul registrului index va conţine o deplasare care în expresia


de adresă, va accesa ultimul element, după care din registrul index se scade o raţie.
Registrul index poate fi utilizat pentru controlul numărului de repetări.
Forma expresiei de adresare cu registrului de index este:

cod_operaţie reg, deplasare [registru_index]


cod_operaţie reg, deplasare+registru_index
cod_operaţie registru_index deplasare reg.

Următoarea secvenţă evidenţiază modul de calcul al adresei pentru


adresarea indexată:

mov cx,20
xor si,si
xor ax,ax
ciclu:
add ax, x[si]
inc si
inc si
loop ciclu

În această secvenţa de program este pusă în evidenţă iniţializarea registrului SI,


utilizarea sa pentru referirea unui element al masivului unidimensional x şi
adăugarea unei raţii.
Secvenţa:
xor si,si
xor ax,ax
ciclu:
add ax,a[si]
inc si
inc si
cmp si,2*(20-1)
jle ciclu

arată cum registrul index este folosit ca registru de control pentru gestionarea
ciclurilor.
În expresie poate lipsi deplasarea, caz în care registrul index trebuie pregătit
ca să refere corect elementele, încărcând adresa primului element ce se accesează.
Secvenţa:
mov cx,20
lea si,x
xor ax,ax
ciclu:
add ax,[si]
add si, 2
loop ciclu
438

utilizează o expresie de referire a elementului unui masiv unidimensional fără


termenul deplasare, acesta fiind inclus deja în registrul si de index.

4.6 Adresarea bazată

În cazul structurilor complexe referirea părţilor şi a elementelor este


realizată diferenţiat. O variabilă de tip articol corespunde unei structuri cu două
nivele. La primul nivel se află întregul, variabila în totalitatea ei, iar la nivelul al
doilea sunt membrii ei. În cazul masivelor bidimensionale, reprezentarea
arborescentă conduce la o structură cu trei nivele. Pe primul nivel se află matricea
ca un întreg, cu toate componentele sale şi se referă ca zonă de memorie nedivizată.
La nivelul al doilea se află liniile matricei, iar pe al treilea nivel se află elementele
acesteia.
Baitul care delimitează începutul zonei de memorie a unei structuri
compuse este considerat bait de bază. Referirile membrilor se efectuează în raport
cu poziţia acestuia. Registrele BX şi BP se utilizează pentru a memora adresele
baiţilor de bază. Aceste registre se numesc din această cauză registre de bază. Dacă
registrul de bază utilizat este BX, registrul de segment pentru evaluarea expresiei
de adresă va fi DS. Prin utilizarea registrului de bază BP, registrul de segment este
SS, deci se va lucra pe stivă.
Ca şi în cazul adresării indexate, registrul de bază ce se iniţializează, este
utilizat pentru referire şi este incrementat sau decrementat sau se utilizează
deplasări în mod adecvat, care la evaluarea expresiei de adresă vor referi corect
membrul cerut din structura compusă.

mov AX, [BX]+2


1
BX 00 04 AX FC 4A
2
 4
3
4A FC
01 02 03 04 05 06 07 08 09 0A 0B 0C

Figura 4.3 – Calculul adresei în cazul adresării bazate

Secvenţa:

mov bp, offset alfa


mov ax, bp + 2
add ax, bp + 8
add ax, bp + 144
439

exemplifică referire de membri cu poziţii neregulate în structură.


Dacă structura compusă nu are un grad de omogenitate scăzut, registrul de
bază, împreună cu deplasarea referă elementele ca în expresii de forma:

mov ax, beta[bp + 52]


mov bx, gama + bp
mov cx, delta + bx + 54

4.7 Adresarea bazată şi indexată

Structurile de date definite pe mai multe nivele necesită expresii complexe


pentru referirea elementelor. Astfel, pentru referirea unui element dintr-o matrice
este necesară poziţionarea pe linia matricei şi apoi în cadrul liniei, pe element.
Formula de referire a elementului aij al unei matrice cu m linii şi n coloane
este:

adresa(aij) = adresa(a00) + (i*n + j)*lungime_element.

Adresa primului element este reper fix, iar adresa de început a liniei i-1 este
adresă de bază pentru referirea unui element.
În formulă apar două progresii aritmetice, una referă linia, a doua referă
elementul în cadrul liniei. Expresia de adresă creşte în complexitate. Mai mult, în
timp ce un registru operand în expresia de adresă are conţinut crescător, celălalt
poate avea conţinut crescător sau descrescător. Traversarea structurilor complexe se
va efectua în direcţii compuse, ceea ce permite realizarea unor algoritmi de referire
cu grad de generalitate ridicat.
Expresiile de adresă au una din structurile:

bp + di
bx + si
bp [di]
bx [si]
reg_bază + reg_index + deplasare
deplasare reg_bază + reg_index
deplasare [reg_bază] [reg_index].

Secvenţa:
xor bx, bx
xor si, si
xor ax, ax
mov cx, nr_linii
ciclul_1: push cx
mov cx, nr_coloane
ciclul_2 add ax, x [bx] [si]
add si, 2
440
loop ciclul_2
xor si, si
pop cx
mov dx, nr_coloane
shl dx,2
add bx, dx
loop ciclul_1

exemplifică accesarea elementelor unei structuri cu trei nivele şi utilizează registrul


de bază BX, pentru accesarea liniilor (elementele de pe al doilea nivel) şi registrul
index SI pentru accesarea membrilor de pe ultimul nivel.

4.8 Adresarea indirectă

Utilizarea variabilelor pointer presupune implementarea modului de


adresare indirectă. Adresarea indirectă presupune ca un registru sau o zonă de
memorie să conţină adresa (deplasamentul) unei variabile. De asemenea, adresarea
indirectă presupune existenţa unui operator care să realizeze extragerea
conţinutului din acea zonă de memorie şi să-l interpreteze ca deplasament.

mov AX, [BX]


1

BX 00 02 AX E5 26
2 3

26 E5
00 01 02 03 04

Figura 4.4 – Calculul adresei în cazul adresării indirecte

Dacă se consideră secvenţa:

mov si, offset x


xor bx, bx
xor ax, ax
add ax, [bx + si]

aceasta conţine evaluarea de adresă BX + SI care înseamnă: conţinutul lui BX se


adună cu conţinutul registrului SI şi rezultă adresa operandului al cărui conţinut
este adunat la registrul acumulator AX, deci registrul SI conţine un deplasament
care localizează operandului care e folosit la adunare.
Sunt limbaje de asamblare în care modul de adresare indirect este
implementat, în acest scop utilizându-se un operator. Se presupune că este definit
441

limbajul de asamblare XX, iar operatorul de indirectare este *. În această situaţie se


impune restructurarea formei interne a instrucţiunii ca să accepte descrierea
distinctă pentru adresarea directă, şi respectiv, pentru adresarea indirectă (un bit cu
valoare zero – adresare directă, unu – adresare indirectă).
Operatorul * este utilizat şi pentru construirea de secvenţe ce corespund
indirectării pe mai multe nivele. Astfel secvenţa:

mov ax, offset a


mov adr_a, ax
mov ax, offset adr_a
mov adr_adr_a, ax
mov ax, offset adr_adr_a
mov adr_adr_a, ax
mov bx, *** adr_adr_adr_a

pregăteşte mai întâi variabilele pointer. Variabila pointer adr_a va conţine adresa
operandului a. Variabila adr_adr_a va conţine adresa variabilei pointer adr_a.
Variabila adr_adr_adr_a conţine adresa variabilei pointer spre pointer spre variabila
a.
Ultima instrucţiune din secvenţă are ca efect încărcarea în registrul BX a
conţinutului zonei de memorie (a) a cărei adresă se află într-o zonă de memorie
(adr_a), a cărei adresă la rândul ei se află într-o zonă de memorie (adr_adr_a), a
cărei adresă se află în zona de memorie adr_adr_adr_a. Cele trei asteriscuri pun în
evidenţă că este vorba de o adresare indirectă cu trei nivele de indirectare. Deci în
structura instrucţiunii, dacă este adresare indirectă, al localizarea operandului se va
specifica dacă acesta este sau nu pointer, pentru a continua sau a întrerupe procesul
de referire.
Adresarea indirectă poate fi simplă, adresare indirectă indexată şi adresare
indirectă bazată. Toate aceste mecanisme de indirectare pot fi implementate în
limbajele evoluate şi cu setul de facilităţi de evaluare a expresiilor de adresă
existent acum la microprocesorul 80x86.
Expresiile de evaluare a adreselor pot fi dezvoltate mult în sensul
specificării momentului în care intervin registrele index sau de bază. Astfel, se
poate proiecta un limbaj de asamblare în care expresia:

** alfa  bp si

să fie interpretată astfel:


 alfa – variabilă pointer spre pointer, conţine adresa unei zone ce conţine
la rândul ei adresa unei zone de memorie;
 BP – registrul de bază care conţine adresa unei zone de memorie, zonă
ce conţine o deplasare relativă;
 SI – registrul de index, ce conţine o deplasare faţă de deplasarea relativă
identificată în zona de memorie localizată prin registrul BP.
442

Mai întâi folosind variabila alfa (acum alfa nu este o deplasare) se găseşte
variabila ce conţine o adresă. La această adresă se adaugă conţinutul zonei de
memorie a cărei adresă s-a aflat în registrul BP, obţinându-se o nouă adresă. Este
adresa unei zone de memorie ce conţine o adresă (al doilea nivel de indirectare). La
această adresă se adaugă conţinutul registrului index şi rezultă adresa operandului
cu care se va lucra de fapt.
Fiecare mod de adresare este însoţit de un număr de cicluri maşină.
Alegerea unui anumit mod este dictată de structurile de date cu care se operează.
Expresia de adresă, ca sumă de un număr de termeni fixat se particularizează
funcţie de codul specificat pe biţii 2, 1, 0 ai baitului MODRM (v. capitolul
Instrucţiuni).
Modurile de adresare trebuie privite ca algoritmi de calcul ai expresiilor de
adresă. Operanzii sunt registre sau numere, în final se obţin adrese ale unor zone de
memorie al căror conţinut este întrebuinţat fie pentru a iniţializa, fie pentru a
modifica (prin adunare, scădere, deplasare, interschimb, conversie) un alt operand,
registru.
Indiferent care este procesorul cu care se lucrează, programatorul în
limbajul de asamblare va cunoaşte restricţiile de utilizare registre şi structurile
posibile ale expresiilor de adresă. Folosirea adecvată a unui mod de adresare se
pune în vedere prin lungimea textului scris şi prin mulţimea de instrucţiuni ce pot fi
înlocuite dacă s-ar folosi un alt mod de adresare, mod care s-ar dovedi mai eficient.
443

5
INDICATORII DE CONDIŢIE
5.1 Registrul FLAGS

Procesoarele din familia x86 dispun de o serie de indicatori de condiţie.


Aceştia reprezintă informaţii legate de starea procesorului şi efectul execuţiei
instrucţiunilor. Fiecare indicator de condiţie ocupă 1 bit în cadrul unui registru
special, registrul F. Pentru microprocesorul 8086, structura acestui registru este cea
din figura 5.1.

15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
-- -- -- -- OF DF IF TF SF ZF -- AF -- PF -- CF

Figura 5.1 – Registrul de indicatori de condiţie al procesorului 8086.

Semnificaţia indicatorilor de condiţie este următoarea:


 CF (Carry Flag, indicator de transport) semnifică un transport sau un
împrumut în, respectiv din bitul cel mai semnificativ al rezultatului.
 PF (Parity Flag, indicator de paritate) este poziţionat după cum biţii din
cel mai puţin semnificativ octet al rezultatului sunt în număr par
(poziţionare pe 1) sau impar (poziţionare pe 0).
 AF (Adjust Flag, indicator de ajustare) este folosit în aritmetica
zecimală şi semnifică un transport sau un împrumut în, respectiv din
bitul 4 (cel mai semnificativ bit al jumătăţii celei mai puţin
semnificative) al rezultatului.
 ZF (Zero Flag, indicator de zero) indică dacă rezultatul unei operaţii a
fost sau nu zero.
 SF (Sign Flag, indicator de semn) are aceeaşi valoare ca bitul cel mai
semnificativ al rezultatului (bitul de semn): 0 - pozitiv, 1 - negativ.
 TF (Trap Flag, indicator de urmărire a execuţiei) este folosit la
depanarea programelor prin execuţia lor pas cu pas - dacă este setat,
procesorul forţează automat o excepţie după execuţia fiecărei
instrucţiuni.
444

 IF (Interrupt Flag, indicator de întreruperi) precizează dacă procesorul


ia în considerare sau nu întreruperile externe.
 DF (Direction Flag, indicator de direcţie) precizează sensul (0 -
crescător sau 1 - descrescător) în care este modificat contorul de adrese
la operaţiile cu şiruri.
 OF (Overflow Flag, indicator de depăşire) semnifică depăşirea
domeniului admisibil la reprezentarea rezultatului unei operaţii
aritmetice cu sau fără semn. Practic, este poziţionat pe 1 dacă apare un
transport înspre bitul cel mai semnificativ al rezultatului din bitul vecin,
dar nu şi din bitul cel mai semnificativ spre CF sau invers, dinspre bitul
cel mai semnificativ spre CF, dar nu şi spre bitul cel mai semnificativ
din bitul vecin. Similar, la împrumut, este poziţionat pe 1 dacă apare un
transport de la bitul cel mai semnificativ la bitul vecin, dar nu şi înspre
bitul cel mai semnificativ dinspre CF sau invers, dinspre CF spre
b.c.m.s., dar nu şi dinspre bitul cel mai semnificativ spre bitul vecin.

Indicatorii CF, PF, AF, SF, ZF, TF şi OF se mai numesc indicatori de stare;
DF este indicator de control, iar IF indicator de sistem.
Odată cu evoluţia procesoarelor din gama x86, au fost introduşi noi
indicatori. Astfel, la procesoarele 80386, registrul FLAGS este extins la 32 de biţi,
formând noul registru EFLAGS, a cărui structură este reprezentată în figura 5.2.

<- Registrul FLAGS pe 16 biţi ->


31-18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
0 VM RF 0 NT IO PL OF DF IF TF SF ZF 0 AF 0 PF 1 CF

Figura 5.2 – Registrul de indicatori de condiţie al procesorului 80386

Noii indicatori au următoarele semnificaţii:


 IOPL (Input-Output Privilege Level, nivel de prioritate al operaţiei de
intrare-ieşire) indică nivelul de prioritate al operaţiei de intrare-ieşire.
 NT (Nested Task, procese imbricate) este folosit pentru controlul
înlănţuirii proceselor imbricate, influenţând operaţiile executate de
instrucţiunea IRET.
 RF (Resume Flag, indicator de reluare) este folosit pentru a inhiba
temporar excepţiile de depanare, permiţând reluarea execuţiei unei
instrucţiuni fără a genera imediat o nouă excepţie.
 VM (Virtual 8086 Mode, mod virtual 8086) indică execuţia unui proces
în modul virtual 8086.
Toţi indicatorii adăugaţi sunt indicatori de sistem. Biţii pe care apar valorile
0 sau 1 sunt rezervaţi de către producător şi nu trebuiesc folosiţi.
445

Pentru relaţia cu coprocesorul matematic există de asemenea o altă serie de


indicatori, grupaţi în registrul de control CR0. Structura acestuia este reprezentată
în figura 5.3.
Semnificaţia indicatorilor este următoarea:
 PE (Protection Enabled, protecţie activată) forţează execuţia
instrucţiunilor în mod real (0) sau protejat (1) de adresare.
 MP (Math Present, coprocesor prezent) controlează execuţia
instrucţiunii WAIT, folosită pentru coordonarea coprocesorului
matematic.
 EM (Emulation, emulare) indică dacă trebuie sau nu emulat
coprocesorul matematic.
 TS (Task Switch, schimbare de proces) este setat de procesor la fiecare
schimbare de proces şi testat la interpretarea instrucţiunilor pentru
coprocesor.
 ET (Extension Type, tip coprocesor) indică tipul de coprocesor prezent
în sistem (80287 sau 80387).
 PG (PaGing, paginare) indică faptul că procesorul foloseşte tabele de
pagini pentru translatarea adreselor liniare în adrese fizice.

31 30-05 04 03 02 01 00
PG -- ET TS EM MP PE

Figura 5.3 – Registrul de control CR0.

Acest registru este accesibil pentru citire şi modificare doar prin intermediul
instrucţiunii MOV, deci prin transferul conţinutului său într-un sau dintr-un registru
de uz general.

5.2 Operaţii cu indicatorii de condiţie

Programatorii folosesc mai multe categorii de operaţii cu indicatorii de


condiţie:
 operaţii de setare / resetare a unor indicatori înaintea unor instrucţiuni
cu scopul de a afecta modul de execuţie a acestora.
 operaţii de setare / resetare a indicatorului CF înaintea unor instrucţiuni
de rotaţie, adunare cu transport sau scădere cu împrumut, pentru a
obţine anumite rezultate.
 operaţii de evaluare a stării unor indicatori pentru a trage concluzii
asupra rezultatelor instrucţiunilor anterioare.
 operaţii de salvare / restaurare a registrului de indicatori, cu scopul
conservării / modificării stării reflectate de indicatori.
446

Instrucţiunile de control al indicatorilor sunt următoarele:

STC (SeT Carry flag, setează indicatorul de transport) are ca efect CF=1.
CLC (CLear Carry flag, resetează indicatorul de transport) are ca efect
CF=0.
CMC (CoMplement Carry flag, complementează indicatorul de transport)
are ca efect CF= NOT CF.
STD (SeT Direction flag, setează indicatorul de direcţie) are ca efect DF=1
(contorul de adrese va fi decrementat în operaţiile cu şiruri).
CLD (CLear Direction flag, resetează indicatorul de direcţie) are ca efect
DF=0 (contorul de adrese va fi incrementat în operaţiile cu şiruri).
STI (SeT Interrupt flag, setează indicatorul de întreruperi) are ca efect
IF=1.
CLI (CLear Interrupt flag, resetează indicatorul de întreruperi) are ca efect
IF=0.
LAHF (Load AH from Flags, încarcă AH din registrul de indicatori) are ca
efect transferul indicatorilor SF, ZF, AF, PF, CF în biţii 7,6,4,2, respectiv 0 ai
registrului AH.
SAHF (Store AH into Flags, încarcă registrul de indicatori din AH) are ca
efect transferul biţilor 7,6,4,2 şi 0 ai registrului AH în indicatorii SF, ZF, AF, PF,
respectiv CF.
PUSHF şi POPF stochează, respectiv refac registrul de indicatori în,
respectiv din vârful stivei. Aceste două operaţii sunt folosite atât pentru
conservarea stării procesorului prin salvare, cât şi pentru examinarea acesteia.
Variantele PUSHFD şi POPFD lucrează pe 32 de biţi, dar nu afectează indicatorii
RF şi VM, aflaţi în cuvântul mai semnificativ al registrului extins de indicatori.

5.3 Poziţionarea indicatorilor de condiţie la execuţie

Pentru lucrul individual cu ceilalţi indicatori nu există instrucţiuni specifice.


Rezultatele dorite se pot obţine însă prin executarea unor secvenţe de operaţii.
De exemplu, dacă se doreşte setarea indicatorului ZF, se execută o operaţie
care să producă un rezultat nul şi să nu afecteze regiştrii cu care se lucrează în acel
moment, ca de exemplu XOR CL,CL.

5.4 Interacţiunea indicatori de condiţie - instrucţiuni

Pentru o programare corectă şi o folosire eficientă a efectelor interacţiunii


indicatori de condiţie - instrucţiuni este necesară cunoaşterea efectelor pe care le au
unele asupra celorlalte.
447

Astfel, indicatorii de sistem au efect asupra modului de execuţie a


instrucţiunilor, iar indicatorii de stare influenţează operaţiile matematice şi salturile
şi apelurile condiţionate.
Interacţiunea indicatori de stare - instrucţiuni este prezentată în tabelul 5.1.

Tabelul 5.1
Indicatorul OF SF ZF AF P CF
Instrucţiunea F
AAA -- -- -- TM -- M
AAS -- -- -- TM -- M
AAD -- M M -- M --
AAM -- M M -- M --
DAA -- M M TM M TM
DAS -- M M TM M TM
ADC M M M M M TM
ADD M M M M M M
SBB M M M M M TM
SUB M M M M M M
CMP M M M M M M
CMPS M M M M M M
SCAS M M M M M M
NEG M M M M M M
DEC M M M M M
INC M M M M M
IMUL M -- -- -- -- M
MUL M -- -- -- -- M
RCL/RCR 1 M TM
RCL/RCR cont -- TM
ROL/ROR 1 M M
ROL/ROR cont -- M
SAL/SAR/SHL/SH M M M -- M M
SAL/SAR/SHL/SH -- M M -- M M
SHLD/SHRD -- M M -- M M
BSF/BSR -- -- M -- -- --
BT/BTS/BTR/BTC -- -- -- -- -- M
AND 0 M M -- M 0
OR 0 M M -- M 0
TEST 0 M M -- M 0
XOR 0 M M -- M 0

Semnificaţia simbolurilor este următoarea:

M - instrucţiunea modifică indicatorul.


448

T - instrucţiunea testează indicatorul.


-- - efectul instrucţiunii asupra indicatorului este nedefinit.
0 - instrucţiunea resetează indicatorul.
Spaţiu - instrucţiunea nu afectează indicatorul.

Pentru o listă completă, care include atât indicatorii de sistem, cât şi alte
instrucţiuni decât cele aritmetice, de rotire şi salt, se poate consulta tabelul 5.2.

Tabelul 5.2
Indicatorul
OF SF ZF AF PF CF TF IF DF NT RF
Instrucţiunea
AAA -- -- -- TM -- M
AAD -- M M -- M --
AAM -- M M -- M --
AAS -- -- -- TM -- M
ADC M M M M M TM
ADD M M M M M M
AND 0 M M -- M 0
ARPL M
BOUND
BSF/BSR -- -- M -- -- --
BT/BTS/BTR/ -- -- -- -- -- M
BTC
CALL
CBW
CLC 0
CLD 0
CLI 0
CLTS
CMC M
CMP M M M M M M
CMPS M M M M M M T
CWD
DAA -- M M TM M TM
DAS -- M M TM M TM
DEC M M M M M
DIV -- -- -- -- -- --
ENTER
ESC
HLT
IDIV -- -- -- -- -- --
IMUL M -- -- -- -- M
IN
INC M M M M M
449

INS T
INT 0 0
INTO T 0 0

Indicatorul
OF SF ZF AF PF CF TF IF DF NT RF
Instrucţiunea
IRET R R R R R R R R R T
Jcond T T T T T
JCXZ
JMP
LAHF
LAR M
LDS/LES/LSS/
LFS/LGS
LEA
LEAVE
LGDT/LIDT/LL
DT/LMSW
LOCK
LODS T
LOOP
LOOPE/LOOP T
NE
LSL M
LTR
MOV
MOV control, -- -- -- -- -- --
debug
MOVS T
MOVSX/MOV
ZX
MUL M -- -- -- -- M
NEG M M M M M M
NOP
NOT
OR 0 M M -- M 0
OUT
OUTS T
POP/POPA
POPF R R R R R R R R R R
PUSH/PUSHA
/PUSHF
RCL/RCR 1 M TM
RCL/RCR -- TM
count
REP/REPE/R
EPNE
RET
ROL/ROR 1 M M
ROL/ROR -- M
count
SAHF R R R R R
450

SAL/SAR/SHL M M M -- M M
/SHR 1
SAL/SAR/SHL -- M M -- M M
/SHR count
SBB M M M M M TM
SCAS M M M M M M T
Indicatorul
OF SF ZF AF PF CF TF IF DF NT RF
Instrucţiunea
SET cond T T T T T
SGDT/SIDT/S
LDT/SMSW
SHLD/SHRD -- M M -- M M
STC 1
STD 1
STI 1
STOS T
STR
SUB M M M M M M
TEST 0 M M -- M 0
VERR/VERR M
W
WAIT
XCHG
XLAT
XOR 0 M M -- M 0

Semnificaţia simbolurilor este următoarea:

M - instrucţiunea modifică indicatorul.


T - instrucţiunea testează indicatorul.
R - instrucţiunea reface valoarea anterioară a indicatorului.
-- - efectul instrucţiunii asupra indicatorului este nedefinit.
0 - instrucţiunea resetează indicatorul.
1 - instrucţiunea setează indicatorul.
Spaţiu - instrucţiunea nu afectează indicatorul.

La execuţia instrucţiunilor de salt condiţionat se evaluează expresii


relaţionale logice ce conţin indicatorii ca operanzi. Dacă rezultatul evaluării este 0
nu se execută saltul, iar dacă este 1 se execută saltul.
451

6
INSTRUCŢIUNI

6.1 Clasificarea instrucţiunilor

Există numeroase criterii de clasificare a instrucţiunilor, care nu se exclud.


Aceeaşi instrucţiune este identificată ca aparţinând unei grupe cel puţin din fiecare
împărţire a oricărui criteriu.
După numărul operanzilor instrucţiunile limbajului de asamblare se
grupează în:
 instrucţiuni fără operanzi;
 instrucţiuni cu un operand;
 instrucţiuni cu doi operanzi;
 instrucţiuni cu mai mulţi operanzi.
În mod uzual, instrucţiunile sunt proiectate cu un număr redus de operanzi,
întrucât creşterea numărului de operanzi atrage mărirea gradului de complexitate al
instrucţiunii. În limbajul de asamblare o instrucţiune execută o operaţie elementară,
cu nivel de complexitate redus.
Natura operanzilor grupează instrucţiunile în:
 instrucţiuni de tip R-R, operanzii sunt registre;
 instrucţiuni de tip R-M, un operand este registru, destinaţia, iar
operandul sursă, este în memorie;
 instrucţiuni de tip M-R, operandul destinaţie se află în memorie,
operandul sursă este un registru;
 instrucţiuni de tip R-I, operandul sursă este o constantă imediată, iar
operandul destinaţie este registru;
 instrucţiuni de tip M-I, sursa este constantă imediată, iar destinaţia este
zonă de memorie;
 instrucţiuni de tip M-M, operanzii se află în memorie.
Limbajele de asamblare se proiectează cu structuri de instrucţiuni care să
gestioneze numai o zonă de memorie, deci instrucţiunile de tip M-M sunt rar
întâlnite sau excluse.
Lungimea zonei de memorie ocupată clasifică instrucţiunile astfel:
 instrucţiuni pe un bait, în care operanzii au poziţie fixată, efectul este
unic;
 instrucţiuni definite pe doi baiţi, de regulă au ca operanzi registre;
452

 instrucţiuni cu trei baiţi, în care un operand este un registru, iar celălalt


poate fi definit ca valoare sau ca deplasare pe un bait;
 instrucţiuni pe patru baiţi, un operand este un registru, iar celălalt este
definit ca valoare sau deplasare pe doi baiţi.
Se pot defini instrucţiuni şi pe un număr superior de baiţi, de fiecare dată
alocarea fiind determinată de natura operanzilor din expresiile de evaluare a
adreselor sursei / destinaţiei.
În raport cu modul în care sunt prelucrate de către asamblor,
instrucţiunile sunt:
 instrucţiuni executabile, cărora asamblorul le dezvoltă un cod pe unul,
doi, sau mai mulţi baiţi, la execuţia programului efectuează transformări
de conţinut asupra registrelor sau zonelor de memorie, selectează alte
instrucţiuni sau testează indicatorii de condiţie;
 instrucţiuni neexecutabile sau directive destinate alocării de memorie,
definirii de constante, gestionării contorului de locaţii, delimitării de
segmente, de proceduri, de macrodefiniţii, punerii în corespondenţa a
constantelor cu nume simbolice, a registrelor cu numele de segment,
stabileşte tipul unei etichete, alinieri pentru adrese.
Instrucţiunile executabile sunt foarte diverse, puterea limbajului de
asamblare fiind asigurată de ortogonalitatea lor. Instrucţiunile neexecutabile
contribuie la implementarea tuturor structurilor de date, la realizarea tuturor
combinaţiilor de structuri de programe şi la gestionarea riguroasă a resurselor
(registre şi memorie).
Instrucţiunile executabile se clasifică după tipul operaţiei în:
 instrucţiuni de transfer de date sau adrese numite surse, spre registre sau
zone de memorie, numite destinaţie;
 instrucţiuni aritmetice, pentru adunări, înmulţiri, scăderi, împărţiri şi
pentru ajustări;
 instrucţiuni de manipulare la nivel de biţi, efectuează deplasări, rotiri,
operaţii logice pe biţi;
 instrucţiuni de manipulare a şirurilor de caractere efectuează copierea a
două zone de memorie, compararea de şiruri, încărcare şi memorare de
caractere sau cuvinte cu sau fără testare;
 instrucţiuni de control destinate apelului şi revenirii din procedură,
salturi condiţionate şi salt necondiţionat, repetări secvenţă sub controlul
unui registru;
 instrucţiuni de poziţionare a indicatorilor de condiţie.
După numărul ciclurilor maşină instrucţiunile se grupează în:
 instrucţiuni cu număr fix de cicluri maşină din care fac parte
instrucţiunile în care operanzii fie lipsesc, fie sunt numai registre;
453

 instrucţiuni cu număr variabil de cicluri maşină; pentru fiecare


instrucţiune este dat numărul de cicluri specific la care se adaugă
ciclurile necesare evaluării şi localizării adresei operandului.

6.2 Descrierea instrucţiunilor

Programatorul trebuie să cunoască exact semnificaţia instrucţiunilor pe care


le utilizează şi mai ales efectele secundare, concretizate prin poziţionarea
indicatorilor de condiţie. Prezentarea instrucţiunilor prin intermediul unui limbaj de
programare evoluat are avantajul ordonării clare a operaţiilor elementare în
totalitatea lor.
De exemplu, instrucţiunea SAL (Shift Arithmetic Left) este descrisă cu
instrucţiunile limbajului C astfel:

temp=CONTOR;
while(temp){
CF=High_Bit(Operand);
Operand*=2;
temp--;
if(CONTOR= =1)
if(High_Bit(Operand)!=CF)
OF=1;
else
OF=0;
else
OF=CF;
}

unde:
temp - variabilă de lucru;
CONTOR - indică numărul de poziţii cu care se face deplasarea spre
stânga; pentru valori diferite de 1, stocarea se va efectua în
registrul CL;
CF, OF - indicatori de condiţie;
High_Bit - funcţie care returnează valoarea bitului cel mai
semnificativ al operandului.
Această instrucţiune are efectul înmulţirii operandului cu 2 la puterea
CONTOR.
Reprezentarea sub formă grafică a mecanismului unei instrucţiuni este
sugestiv, având însă pierderi de informaţii de detaliu asupra modului în care
decurge realizarea operaţiei. Pentru instrucţiunea

shl operand, contor

în care Operand este registrul AL, reprezentarea grafică este dată în figura 6.1.
454

7 0
C   0
CF AL

Figura 6.1. Deplasare spre stânga

Dacă registrul CL asociat variabilei CONTOR conform restricţiilor de


implementare ale instrucţiunii conţine valoarea 3, după efectuarea deplasării spre
stânga, registrul AL va conţine 01001000.
Reprezentarea tabelară a instrucţiunilor ia în considerare variantele ce pot fi
utilizate de programator şi numărul de cicluri necesar. O prezentare generalizată
ţine seama şi de tipul de microprocesor utilizat, pentru că diferenţele pentru fiecare
se impun a fi specificate.
Astfel, pentru instrucţiunea SHL se va alcătui tabelul 6.1.

Tabelul 6.1.
Număr cicluri maşină
Mod adresare Exemplu
286 386 486
Reg,1 SHL bx,1 2 3 3
Mem,1 SHL alfa,1 7 7 4
Reg,CL SHL ax,cl 5+n 3 3
Mem,CL SHL alfa,cl 8+n 7 4
Reg,imediat8 SHL bx,5 5+n 3 2
Mem,imediat8 SHL alfa,5 8+n 7 4

În tabelul 6.1. s-au utilizat notaţii cu semnificaţiile:


Reg - registru de lucru;
Mem - variabilă definită în memorie;
CL - registrul CL;
N - numărul de poziţii cu care se face deplasarea.
Pentru descrierea instrucţiunilor se construiesc limbaje care să aibă
capacitatea de a reda exact semnificaţia operaţiei asociate fiecărui mnemonic.
Un registru este format din biţii b0, b1, b2, …, b15. Instrucţiunea de deplasare
shl bx,1 este descrisă folosind ecuaţii recurente astfel:
CF=b15
bi+1=bi
b0=0
pentru i=0, 1, 2, …, 14.
Dacă apar unele condiţionări şi neregularităţi de comportament pe mulţimea
biţilor sau se lucrează cu anumite grupuri de biţi, prezentarea devine mai dificil de
urmărit. În plus, pentru a marca deosebirea biţilor dintr-un registru de biţii dintr-o
455

zonă de memorie se vor face notaţii adecvate. Restricţiile noului limbaj, de cele
mai multe ori nu se regăsesc în descrierile altor limbaje de asamblare pentru a
uşura învăţarea sau compararea lor.
Se va putea vorbi de o descriere bună a instrucţiunilor dacă:
 este prezentată mnemonica şi semnificaţia fiecărei litere;
 printr-un text se specifică operaţia pe care o execută fiecare instrucţiune;
 se detaliază fiecare formă de adresare a operanzilor admisă şi se
exemplifică efectele pe care execuţia instrucţiunilor le are asupra
operanzilor;
 se specifică numărul de cicluri maşină necesare execuţiei fiecărei
variante de definire a expresiei de adresare a operanzilor;
 se dau reprezentările la nivel de bit ale dispunerii informaţiilor din linia
sursă, după asamblare;
 se precizează poziţionarea indicatorilor de condiţie.
Manualele care descriu limbaje de asamblare îmbină toate modurile de
prezentare pentru a asigura atât rigurozitate, cât şi de a face limbajul de asamblare
mai atractiv, mai uşor de învăţat şi de a utiliza cât mai potrivit instrucţiunile.

6.3 Forma externă a instrucţiunilor

Programul scris în limbaj de asamblare este format dintr-o succesiune de


instrucţiuni. Ceea ce scrie programatorul sunt forme concrete de prezentare a
instrucţiunilor sau forme externe.
O linie sursă conţine:
 eticheta instrucţiunii (opţional);
 codul operaţiei;
 lista de operanzi;
 comentariu (opţional).
Regulile de construire a etichetelor sunt comune şi pentru identificatori.
Lungimea cuvântului asociat etichetei este strict legată de dorinţa programatorului
de a da lizibilitate programului.
Dacă se construiesc etichete cu o destinaţie specială, reutilizate într-un
context anume, este folosit caracterul blanc subliniat (underscore). Astfel,
construcţiile __alfa__ , _beta, gama _ sunt etichete din categoria menţionată. În
programe se întâlnesc variabile globale definite în fişierele unor produse program
de largă circulaţie, în expresii de adresă.
În mod curent se definesc etichete precum: ciclu, start, final, adună, alege.
Pentru a mări lizibilitatea se fac concatenări de nume şi delimitarea este obţinută
prin utilizarea de litere mari. Astfel, etichetele AlegeMaxim, PretUnitar,
ExtrageBait, SirCifric, utilizate corespunzător împreună cu comentariile de pe
fiecare linie ajută programatorul la depanarea sau la dezvoltarea programului.
456

Codul operaţiei este de fapt o mnemonică. Programatorul care a lucrat într-


un limbaj de asamblare va observa că există diferenţe minore la alcătuirea
mnemonicelor. De aceea, cunoscând un vocabular al operaţiilor fundamentale,
generarea de mnemonice devine un proces natural.
În tabelul 6.2 sunt prezentate câteva din mnemonicele utilizate în timp
pentru operaţiile cele mai frecvente din programe.

Tabelul 6.2.
Mnemonicele limbajelor
Operaţia ASSEMBLER-
ASSIRIS MACRO11 ASM
IBM
Addition A,AR,AD AD4,AD8 ADD ADD
Move MVC,MVI MVZ MVSR,MVSL MOV MOV
Jump B BRU BR JMP
Compare C,CD,CDR,CE CP4,CP2 CMP CMP
Shift SLA,SRA SLAx,SRAx ASL,ASR SAL,SAR
Increment IC2,IC4 INC INC

Construirea mnemonicelor fără a include lungimi ale zonelor de memorie


deplasează informaţiile spre cei doi operanzi; operandul cu lungimea cea mai mică
dacă este receptor impune lungimea sursei. Dacă operandul sursă are o lungime
mai mică se va produce o preluare din zone învecinate, cu alterarea rezultatelor. În
cazul limbajelor cu o diversitate restrânsă de tipuri de operanzi mnemonicele nu
conţin date despre lungimile operanzilor.
Lista de operanzi este de fapt o listă de expresii cu care se referă operanzii.
Faptul că registrele generale au asociate numele AX, BX, CX, DX sau AH, AL,
BH, BL, CH, CL înseamnă că adrese anumite sunt puse în corespondenţă cu aceste
nume. Şi în cazul referirii registrelor este vorba de a evalua o expresie - expresia cu
un singur termen, numele asociat registrului.
Complexitatea expresiilor de evaluat pentru a obţine adresa unui operand
diferă în funcţie de multitudinea şi diversitatea operatorilor care pot fi utilizaţi.
Indiferent care este nivelul de complexitate al expresiilor, condiţia impusă este ca
evaluarea să se efectueze în procesul de asamblare. Toţi operanzii trebuie să fie
complet definiţi ca valori atunci când intervin într-o expresie din lista de operanzi.
Şi generaţiile mai vechi de limbaje de asamblare permiteau construirea de
expresii în care apăreau operatorii aritmetici +, -, *, /, MOD (pentru obţinerea
restului împărţirii a doi termeni). Astfel, dacă se defineşte secvenţa:

a equ 15
b equ a + 4*6
c equ a + b

se va instrucţiunea:
457

mov ax, 2*b+c+a mod 4

care este echivalentă cu:

mov ax, 135

pentru că lui B i se asociază 15+24=39, lui C i se asociază 54, iar A MOD 4


înseamnă rest 3; 2*39+54+3=135.
Dacă în expresii apar operatori care lucrează cu şiruri de biţi, precum OR,
AND, XOR, NOT (OR – sau logic, AND – şi logic, XOR – sau exclusiv, NOT –
complement faţă de unu) expresiile cresc în complexitate. Astfel instrucţiunea:

add ax, not 10011b and 10101b or 11000b

este echivalentă cu instrucţiunea:

add ax, 11100b

pentru că NOT 10011b este egal cu 01100b, 01100b AND 10101b este egal cu
00100b, iar 00100b OR 11000b este egal cu 11100b.
La introducerea operatorilor relaţionali este important să se cunoască
valorile numerice cu care sunt puse în corespondenţă valoarea logică “adevărat” şi,
respectiv, valoarea logică “fals”. În cazul asamblorului pentru limbaj ASM
adevărat înseamnă (-1), iar fals înseamnă (0). Operatorii relaţionali sunt: GT (mai
mare ca), GE (mai mare sau egal), EQ (egal cu), NE (neegal), LT (mai mic decât),
LE (mai mic sau egal).
Instrucţiunea:

mov bx,(7 gt 5)and(8 le 3)

are ca efect încărcarea în registrul BX a valorii zero pentru că la evaluare, 7 GT 5


înseamnă “adevărat”, adică (-1), 8 LE 3 înseamnă “fals”, adică 0. Operatorul logic
AND pe biţi când un termen este cu toţi biţii zero conduce la obţinerea unui şir de
şaisprezece zerouri în registrul BX.
Operatorul TYPE aplicat unei variabile returnează un număr care indică
lungimea ca număr de baiţi ai unui element care alcătuieşte variabila respectivă.
Dacă se consideră definiţiile:

a db ?
b dw l0 dup (0)
str struc
x dw ?
y db 8 dup (?)
458
str ends
alfa str 20 dup (<>)

aplicând operatorul TYPE unora dintre ele în secvenţa:

mov al, type a ; al =1 (db este 1 bait)


mov bl, type b ; bl =2 (dw are 2 baiţi)
mov cl, type alfa ; cl =10 (x are 2 baiţi, y are 8)

Operatorul SIZE returnează lungimea zonei de memorie ocupată de o


variabilă în totalitate.
Dacă se consideră definirea:
st struc
xx db ? ; lungime 1
yy dw ? ; lungime 2
zz dt ? ; lungime 10
st ends
mat st 10 dup (5 dup (<>))

aplicând operatorii TYPE, SIZE şi LENGTH (acest operator returnează numărul de


componente care alcătuiesc masivul), în secvenţa:

mov ax, type mat ; ax=1+2+10


mov bx, length mat ; bx=50 (10*5 componente)
mov cx, size mat ; cx=50*13 baiţi rezervaţi.

Aceşti operatori permit o creştere a capacităţii de mentenanţă a textului la


modificările de structură ale datelor, ştiut fiind faptul că în multe situaţii trebuie
contorizate elementele şi trebuie traversate zone de memorie ale căror lungimi
rezultă din calcul. Dacă se cunoaşte adresa de început şi lungimea zonei poate fi
calculată adresa ultimului bait, pentru a face traversarea de la acesta către primul
bait, mod de lucru des întrebuinţat.
Operatorii HIGH şi LOW returnează baitul cel mai semnificativ, respectiv,
baitul cel mai puţin semnificativ al unei date cu un tip specificat.
Din secvenţa:
număr dd 12345678h
……
mov ax, word high număr ; ax=1234
mov bx, word low număr ; bx=5678
mov al, byte high word low număr ; al=56

se observă capacitatea de a selecta părţi dintr-un întreg, specificate cu atributele


BYTE şi WORD, ce corespund tipurilor fundamentale de date ale limbajului de
asamblare, alături de DWORD, QWORD, TBYTE. Când se defineşte o variabilă se
459

specifică numele, tipul şi eventual valoarea cu care este iniţializată zona de


memorie.
În secvenţa:

a dw 7
b db ‘abcd’
c struc
d db ?
x dd 11 dup (?)
c ends
w c 10 dup ()

a este o variabilă de tip cuvânt, b este variabilă de tip bait, w este variabilă de tip c.
Utilizarea numelui unei variabile înseamnă asocierea cu întreaga zonă de memorie
alocată, 2 baiţi pentru variabila a, 1*4 baiţi pentru variabila b şi 450 baiţi pentru
variabila w. Totuşi, orice variabilă chiar dacă are un tip, este privită ca zonă de
memorie. Există posibilitatea de a face conversii de tip aşa fel încât variabila a
definită iniţial ca un cuvânt să poată fi tratată ca un şir de 2 baiţi aşa cum este
variabila b. Tot astfel se pune problema de a face o conversie ca variabila w să fie
privită fie ca un şir de cuvinte, fie ca un şir de baiţi. Conversia de tip presupune
utilizarea operatorului PTR într-o construcţie de forma:

expresie_1 ptr expresie_2

care realizează conversia de la tipul dat după evaluarea expresiei-2 la tipul cerut de
expresia-1.

Dacă se consideră definirea:

aa dd 12345678h

secvenţa:

mov al, byte ptr aa ;al:=78h


and byte ptr [si], 0fah ;operaţie pe 8 biţi
and byte ptr aa, 0aah ;operaţie 8 biţi, deplasare 16 biţi

pune în evidenţă că deşi variabila aa este definită dublu cuvânt se explorează din ea
numai un bait, presupunând că registrul SI conţine deplasamentul lui aa.
Operatorii SEG şi OFFSET returnează adresa de început a segmentului
unde se află operandul, respectiv, deplasarea pe care o are operandul faţă de
începutul segmentului.
Dacă o variabilă xx este definită în segmentul de date SEGDAT,
iniţializarea registrului de segment DS se efectuează prin:
460

mov ax, seg xx


mov ds, ax

În cadrul expresiilor apar operatorii (, ), [, ] şi operatorul punct, utilizat


pentru referirea membrilor unor structuri de date de tip articol. Secvenţa:

add ax, (5+3)*(7-2) ;parantezele modifică priorităţile


;ca în algebră
mov bx, [bx+si+4*5] ;[, ] se folosesc pentru evaluare
;şi extragere conţinut
mov cx, xxx.alfa ;alfa e membru în structura xxx

exemplifică utilizarea acestor operatori.


În tabelul 6.3. este prezentată mulţimea operatorilor, grupaţi pe priorităţi.

Tabelul 6.3.
Priorităţi Operatori
1  () [] LENGTH, MASK, WIDTH
2 . (operatorul de referire membri din structuri)
3 HIGH, LOW
4 +,- (operatori unari)
5 : (specificare segment:offset)
6 OFFSET, PTR, SEG, THIS, TYPE
7 *, /, MOD, SHL, SHR
8 +,- (operatori binari)
9 EQ, NE, GT, GE, LT, LE
10 NOT
11 AND
12 OR, XOR
13 LARGE, SHORT, SMALL

Unii dintre operatori sunt prezentaţi în capitolele următoare pe măsură ce


sunt definite tipuri de date şi directive care impun evaluări de expresii cu
includerea lor.
Se observă că asamblorul este un program extrem de complex din moment
ce permite evaluarea unor expresii de complexitate ridicată, mulţimea operatorilor
fiind comparabilă cu operatorii din limbajul C. Comentariile, ultimul element al
liniei sursă, sunt opţionale. Scrierea de comentarii va uşura munca de dezvoltare a
programului, oferind programatorului explicaţii la sensul operaţiilor, o formulă sau
o valoare care are o semnificaţie particulară pentru program.
461

6.4 Forma internă a instrucţiunilor

Asamblorul evaluează expresiile în măsura în care acest lucru este posibil,


identifică operandul registru sau operandul imediat, stabileşte codul operaţiei. În
final toate elementele formei externe a instrucţiunii se vor codifica. Rezultă un
număr de baiţi ocupaţi cu configuraţii care la execuţia programului se interpretează
şi determină prelucrările dorite de programator.
Codul operaţiei şi unele informaţii în continuare vor indica numărul de baiţi
din care este alcătuită codificarea instrucţiunii. Structura internă a instrucţiunii
conţine informaţii privind:
 codul operaţiei;
 modul de adresare;
 registrele utilizate şi semnificaţia lor;
 lungimea în baiţi a descrierii operandului memorie;
 rolul pe care îl are registrul (sursă sau destinaţie);
 lungimile operanzilor;
 sensul de efectuare a traversării.
În cazul unor limbaje de asamblare în care mnemonica defineşte tipul
operanzilor, lungimea, deci numărul de mnemonice este ridicat, codul asociat
mnemonicelor ocupă un bait. Limbajele de asamblare de acest tip vehiculează cel
mult 256 de instrucţiuni diferite. Se creează în acest fel premisa structurii fixe a
poziţiei câmpurilor din instrucţiune, ceea ce înseamnă pe de o parte un mod simplu
de codificare şi pe de altă parte risipă de memorie. Se va prezenta în continuare
concepţia limbajului de asamblare în care dispunerea informaţiilor se face din
aproape în aproape şi câmpurile se intercondiţionează. Grupele de instrucţiuni se
diferenţiază după primele patru poziţii ale codului operaţiei.
Dacă se construieşte un tabel al frecvenţelor de utilizare pentru instrucţiuni,
este posibilă atribuirea unor coduri care sunt mai deosebite pentru instrucţiunile cu
frecvenţele cele mai mari. Este posibilă construirea unui tabel cu 16 linii şi 16
coloane ce corespund combinaţiilor biţilor cei mai semnificativi din baitul codului
de operaţie, respectiv, biţilor mai puţin semnificativi. Dispunând pe liniile matricei
în continuare, codurile instrucţiunilor în ordinea descrescătoare a frecvenţelor de
utilizare mnemonice, se va obţine o structură avantajoasă şi pentru constructorul de
asambloare eficiente.
Tabelul 6.4. prezintă o dispunere a mnemonicelor, aşa cum rezultă din
codurile deja atribuite de designerul limbajului de asamblare, perfectibilă după
analiza statistică a unui lot reprezentativ de programe.
462

Tabelul 6.4.
Instrucţiune Numărul de apariţii
mov 567
pop 152
cmp 89
push 77
sub 32
ret 28
add 20
jmp 14
call 6
lea 6
xor 4

Structura internă a instrucţiunilor limbajului de asamblare variază funcţie de


modul de adresare, de numărul de operanzi şi de natura operanzilor.
Instrucţiunile care se reprezintă pe un bait şi nu au operanzi sunt destinate
poziţionării pe zero sau pe unu a indicatorilor de condiţie (CLC, STC, CLI, STI, CLD,
STD, CMC), controlează execuţia programului (HLT, NOP, WAIT). Cei 8 biţi pe care
este reprezentată o astfel de instrucţiune sunt interpretaţi cod operaţie, astfel:

bit 7 6 5 4 3 2 1 0
c c c c c c c c

Figura 6.2 – Forma internă a instrucţiunilor pe un bait fără operanzi

unde c reprezintă o cifră binară 0 sau 1 care intră în componenţa codului


instrucţiunii.
Dacă instrucţiunea admite o construcţie de forma:

mnemonica registru

ca de exemplu, instrucţiunile din secvenţa:

push ax
dec cx
inc dx

fiind vorba, deci de instrucţiuni cu un singur operand şi acesta fiind


registru, dispunerea informaţiei pe un bait va conduce la structura internă de
instrucţiune din figura 6.3, unde c reprezintă cifre binare asociate codului operaţiei,
iar r reprezintă cifre binare asociate codurilor registrelor generale. Codurile
asociate registrelor generale sunt date în tabelul 6.5.
463

bit 7 6 5 4 3 2 1 0
c c c c c r r r

Figura 6.3 – Forma internă a instrucţiunilor pe un bait cu un operand

Tabelul 6.5.
Registrul
Cod
Operaţie pe cuvânt Operaţie pe bait
000 AX AL
001 CX CL
010 DX DL
011 BX BL
100 SP AH
101 BP CH
110 SI DH
111 DI BH

Pentru incrementare de exemplu, codul instrucţiunii INC (operaţie pe


cuvânt) este 01000 dacă operandul va fi de tip registru. Instrucţiunea:

inc si

se va codifica astfel:

bit 7 6 5 4 3 2 1 0
0 1 0 0 0 1 1 0
c C c c c r r r

Figura 6.4 – Forma internă a instrucţiunii inc si

Codificarea afişată de asamblor va fi 46h.


Există unele limitări la folosirea operanzilor, în sensul fixării acestora. De
exemplu, instrucţiunea de conversie de la un bait la cuvânt, CBW, presupune ca
baitul să fie memorat în registrul AL, iar rezultatul conversiei se va găsi în registrul
AX.
Instrucţiunile PUSHA, PUSHF, POPA, POPF se prezintă pe un bait pentru că
operanzii lor sunt prefixaţi, toate registrele (PUSHA, POPA), respectiv, biţii
indicatorilor de condiţie (PUSHF, POPF).
Instrucţiunile de lucru cu şiruri de caractere se reprezintă de asemenea, pe
un bait pentru că adresele zonelor de memorie sunt stocate în registrele DS, SI
(zona de memorie destinaţie) şi registrul CX va conţine lungimea zonei sursă. Deşi
464

execută operaţii complexe, instrucţiuni ca LODSB, LODSW, STOSB, STOSW,


SCASB, SCASW, CMPSB, CMPSW, MOVSB, MOVSW, pentru că lucrează cu şiruri de
caractere, având registre prefixate pentru adrese, operanzi, asamblorul le codifică
pe un bait.
Instrucţiunile de tip R-R, R-I, R-M, M-R, M-I presupun informaţii privind
lucrul la nivel de bait sau la nivel de cuvânt. Bitul 0 din primul bait al instrucţiunii
codificat w, dacă are valoarea 0 înseamnă că operanzii sunt pe un bait, iar dacă are
valoarea 1, operanzii sunt pe cuvânt.
Registrul operand poate fi sursă (d=0) sau destinaţie (d=1), unde cu d a fost
notat bitul unu din primul bait al instrucţiunii. Celelalte informaţii necesită încă un
bait, numit MODRM de programatori datorită conţinutului său. Dacă operanzii
sunt de tip imediat sau sunt în memorie şi se definesc cu deplasare, urmează la
codificarea instrucţiunii al treilea bait (d=0) sau al treilea şi al patrulea (d=1).
Baitul codului de instrucţiune se structurează pentru instrucţiunile cu doi
operanzi astfel:

bit 7 6 5 4 3 2 1 0
c c c c c c d w

Figura 6.5 – Forma internă a primului bait al instrucţiunilor pe doi baiţi

bit 7 6 5 4 3 2 1 0
m m R R R r/m

Figura 6.6 – Forma internă a baitului al doilea al instrucţiunilor pe doi baiţi

Al doilea bait asociat unei instrucţiuni după asamblare are structura dată în
figura 6.6, unde:
m – reprezintă biţii 7,6 asociaţi modului în care sunt interpretaţi biţii 2, 1, 0
şi cum este definită deplasarea operandului faţă de începutul
segmentului (pe un bait sau pe doi baiţi), tabelul 6.6;
R – ocupă biţii 5 ,4 , 3 şi indică registrul operand;
r/m – stabileşte formula de calcul a adresei operandului aflat în memorie.
465

Tabelul 6.6.
Biţii 7, 6
Deplasarea
Mm
00 Lipseşte din instrucţiune
01 Este definită pe un bait
10 Este definită pe doi baiţi
11 Biţii 2, 1, 0 se interpretează drept cod registru

Tabelul 6.7.
Biţii 2, 1, 0 Formula de calcul a adresei
000 BX + SI + deplasare
001 BX + DI + deplasare
010 BP + SI + deplasare
011 BP + DI + deplasare
100 SI + deplasare
101 DI + deplasare
110 BP + deplasare
111 BX + deplasare

Deplasarea intră în calcule pe 16 biţi. Dacă biţii mm au valoarea 01 are loc


extensia pe 16 biţi prin propagarea bitului de semn. Dacă biţii mm sunt 00 şi biţii
r/m sunt 110 deplasarea este pe 16 biţi, deci nu lipseşte.
Instrucţiunea:

mov dl, bx[si]

se codifică prin:

ba10h

Cei doi baiţi se interpretează astfel:

7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
c c c c c c d w m m R R R r/m
1 0 1 1 1 0 1 0 0 0 0 1 0 0 0 0

unde:
 biţii notaţi cccccc (101110) reprezintă codul operaţiei MOV registru,
memorie (R - M);
 bitul de direcţie (d=1) arată că registrul este destinaţie;
 bitul tipului de operand (w=0) arată că operandul este un bait;
466

 biţii mm prin codul 00 arată că deplasarea lipseşte din instrucţiune;


 biţii RRR prin valoarea 010 arată că operandul destinaţie registru este
DL (tabelul 6.5.) ;
 biţii r/m prin valoarea 000 pun în evidenţă (tabelul 6.7.) că expresia de
adresă se calculează după formula BX + SI + 0.
Dacă se ia în considerare multitudinea de combinaţii de expresii de
adresare, pentru o decodificare corectă a instrucţiunilor se identifică mai întâi
apartenenţa la unul din tipurile:

T1 - cod operaţie;
T2 - cod operaţie (operanzii sunt prefixaţi);
T3 - cod operaţie registru;
T4 - cod operaţie constantă imediată;
T5 - cod operaţie registru, registru;
T6 - cod operaţie registru, constantă imediată;
T7 - cod operaţie registru, expresie de adresă;
T8 - cod operaţie expresie de adresă;
T9 - cod operaţie expresie de adresă, registru;
T10 - cod operaţie expresie de adresă, constantă imediată.

Numai după aceea se va face distincţia între registrele generale şi registrele


de segmente care au coduri asociate conform tabelului 6.8.

Tabelul 6.8.
Cod Registru segment
00 ES
01 CS
10 SS
11 DS

Apariţia codului registru segment determină ca o zonă de trei biţi să fie completată
0ss, unde ss reprezintă codul registrului segment. Dacă se impune, codul segment
poate ocupa şi poziţiile 4, 3 din baitul codului operaţie.
Se observă că structura internă a instrucţiunii reprezintă un mod
convenţional de punere în corespondenţă a multitudinii de tipuri de instrucţiuni cu
coduri. Se urmăreşte realizarea de combinaţii unice, neambigue, iar asamblorul şi
dezasamblorul să fie aplicaţii, una transformată a celeilalte.
Trecerea la microprocesorul 386 a condus la noi performanţe. Limbajul de
asamblare a rămas în mare parte nemodificat, iar programatorul poate realiza
construcţii noi, putând integra complet pe cele vechi.
În dinamicile hardware, structura internă a instrucţiunilor este afectată cel
mai frecvent. În cazul microprocesorului 386 se specifică următoarele aspecte:
467

 codul operaţiei ocupă doi baiţi, permiţând definirea unor noi variante de
prezentare dar şi deschizând posibilitatea definirii de noi instrucţiuni ca
operaţii sau noi tipuri de instrucţiuni, ca structură de listă de operanzi;
 lungimile operanzilor sunt un bait, doi baiţi şi patru baiţi, lucru ce se
reflectă prin informaţiile descriptorilor d şi w;
 registrele se codifică pentru trei situaţi: registrele de un bait, registrele
pe 16 biţi şi registrele EAX, ECX, EDX, EBX, ESP. EBP, ESI, EDI pe
32 de biţi, cărora li se asociază coduri tot de trei biţi, pe poziţiile din
baitul MODRM.
 biţii r/m iau în considerare toate structurile de expresii de adresă în
varianta lucrului pe 16 biţi, iar separat în varianta lucrului pe 32 de biţi;
 factorul de scală pentru adresarea indexată (biţii 7, 6) din al treilea bait
de descriere a instrucţiunii;
 registrul index (biţii 5, 4, 3) din al treilea bait;
 registrul de bază, următorii biţi, 2, 1, 0;
 deplasarea sau constanta imediată se reprezintă în continuare pe zero
(dacă lipseşte) baiţi, pe un bait, pe doi baiţi sau pe patru baiţi;
 în cazul instrucţiunilor de salt condiţia de testare este înglobată pe patru
biţi în instrucţiune, primii trei biţi definesc combinaţia, iar bitul al
patrulea când este unu determină complementarea faţă de unu a primilor
trei biţi.
Trecerea de la un procesor la altul devine pentru programatorul în limbaj de
asamblare un exerciţiu de codificare cu amplasarea impusă a câmpurilor într-o
succesiune dată de biţi. Mai apare în plus o completare a listei de instrucţiuni ca
mnemonice, cu descrieri riguroase şi eventual noi resurse (registre) sau noi
caracteristici ale resurselor (lucrul pe 32 de biţi).
Prin studierea comparată a structurilor interne ale instrucţiunilor limbajelor
de asamblare, a frecvenţei de utilizare a instrucţiunilor şi a diversităţii
instrucţiunilor şi expresiilor de adresare, este posibilă proiectare unei codificări
optime, care minimizează lungimea textului generat după asamblare, ca număr de
baiţi.

6.5 Comentariile

Programele sunt scrise pentru a rezolva probleme dar şi pentru a fi


dezvoltate în aşa fel încât să continue rezolvarea problemelor, după un număr de
ani, după ce au intervenit modificări în algoritmi, prin efectuarea unor modificări în
definirea operanzilor şi în secvenţele de prelucrare.
Comentariile sunt mai necesare în programele scrise în limbajul de
asamblare pentru caracterul ermetic, codificat al mnemonicelor şi al registrelor.
Este preferabil să se introducă numeroase comentarii ca linii sursă distincte
dar şi în continuarea instrucţiunilor.
468

Astfel, secvenţa:

; programul calculează suma


; a două numere a şi b
; rezultatul se va afla în c
mov ax,a ;ax=a
add ax, b ;ax=ax+b
mov c,ax ;c=ax

va putea fi uşor modificată de un alt programator după câţiva ani de la


scrierea programului.
Secvenţa:
xor bx, 1fach
add bx, x[si] + 0cch
and di – 1dh, 0ff00h

în absenţa explicării semnificaţiei constantelor 1FACh, 0CCh, 1Dh,


0FF00h, va fi dificil pentru programator să interpreteze şi mai ales să actualizeze
aceste constante.
Deşi este un efort suplimentar să se includă într-un text comentarii,
introducerea obligativităţii de a se autodocumenta programele va avea efecte
pozitive asupra procesului de mentenanţă a acestora.

6.6 Efectele execuţiei instrucţiunilor

Instrucţiunile sunt simple sau complexe fie în raport cu modul în care sunt
scrise pe o linie program, fie în raport cu ceea ce efectuează. Este interesant pentru
programator să cunoască complexitatea în raport cu efectele de prelucrare şi să
folosească eficient tot ceea ce oferă instrucţiunile.
Este cunoscut faptul că după efectuarea de calcule are loc poziţionarea
indicatorilor de condiţie. Neglijarea acestui aspect va conduce la scrierea în mod
inutil a unei instrucţiuni de comparare.
Secvenţa:

mov ax, a
sub ax, b
cmp ax, 0
jle alfa

va fi scrisă, cunoscând cum se poziţionează indicatorii de condiţie, astfel:

mov ax, a
sub ax, b
jle alfa
469

În cazul utilizării instrucţiunilor LOOP are loc modificarea conţinutului


registrului contor CX prin decrementare, pentru a gestiona repetările secvenţelor.
Pentru construirea de secvenţe repetitive imbricate (incluse) este necesară salvarea
acestui registru înainte de a intra în secvenţa repetitivă inclusă şi restabilirea după
ieşirea din aceasta.
Instrucţiunile de lucru cu şiruri de caractere sunt utilizate alături de
instrucţiunea de repetare REP. Traversarea zonelor de memorie presupune
incrementarea automată a registrelor index SI şi DI. La terminarea execuţiei, aceste
registre permit accesarea primului bait din afara fiecărei zone. Lucrul cu zone de
memorie adiacente în aceste condiţii nu mai necesită pregătirea registrelor, prin a
încărca deplasamentul zonelor sursă, respectiv, destinaţie. Se va lucra folosind
efectul secundar al instrucţiunii REP.
Multe din instrucţiunile care presupun memorare în stivă (CALL, RET, DIV,
INT, IRET, INTO, PUSH, POP) modifică mai întâi conţinutul registrului SP şi
stochează anumite informaţii. Prin incrementarea sau decrementarea registrului SP
programatorul va avea acces la informaţii stocate automat ca parte din execuţia
unei instrucţiuni.
De asemenea, programatorul are posibilitatea să definească noi combinaţii
în care să se efectueze salturile, testând în mod propriu prin expresiile sale când şi
cum să se selecteze o anumită secvenţă. Alegerea între a lucra pe un bait, pe doi
baiţi sau pe mai mulţi baiţi depinde de natura operanzilor şi de magnitudinea
rezultatului. Pentru a efectua corect operaţiile trebuie cunoscut ce se modifică şi ce
rămâne nemodificat la execuţia unei instrucţiuni.
Dacă un operand are o lungime L1 şi celălalt operand are lungimea L2, când
se efectuează operaţiile, rezultatul are o lungime dependentă de acestea. În cazul
adunării totalul va avea max (L1, L2) +1 cifre, la înmulţire produsul va fi de L1 + L2
cifre, iar la împărţire câtul va fi de L1-L2 cifre. Aceste calcule sunt făcute şi
rezervările de zone de memorie vor acoperi acest tip de cerinţe.
Studierea instrucţiunilor are rolul de a da mobilitate programatorului şi de
a-i creşte capacitatea de adaptare la toate cerinţele realizării de programe
performante, chiar dacă lucrul în limbaj de asamblare e impus şi nu dorit.
470

7
DEFINIREA STRUCTURILOR DE DATE
7.1 Date elementare

Datele elementare sunt asociate unor zone de memorie de lungime fixată


care opţional sunt iniţializate cu constante care impun atât natura operanzilor, cât şi
setul de instrucţiuni cu care se va lucra.
Directivele pentru descrierea datelor elementare determină modificarea
contorului cu o unitate dacă tipul este DB (Define Byte), cu două unităţi dacă tipul
este DW (DefineWord), cu patru unităţi pentru tipul DD (Define Double), cu opt
unităţi pentru tipul DQ (Define Quad Word) şi cu zece unităţi pentru tipul DT
(Define Ten).
Secvenţa:
a db 13 : a ocupă un bait iniţializat cu 13
b dw 5 ; b ocupă 2 baiţi iniţializaţi cu 5
c dd 0fah ; c ocupă 4 baiţi iniţializaţi cu 0fah
d dt 0 ; d ocupă 10 baiţi iniţializaţi cu 0
e dq 7 ; e ocupă 8 baiţi iniţializaţi cu 7
f dw ? ; f ocupă 2 baiţi neiniţializaţi
g db ? ; g ocupă un bait neiniţializat

exemplifică definiri de variabile elementare cu sau fără iniţializare. Alegerea


numelui de dată elementară va fi sugestiv şi se va căuta să nu fie întrebuinţată
aceeaşi variabilă pentru mai multe categorii de rezultate în acelaşi program.
Alegerea tipului DB este utilă dacă datele au valori cuprinse între 0 şi 255.
Descriptorul DW este preferat când variabila are valori cuprinse între 0 şi 65535.
Tipul de dată DD este utilizat când datele sunt cuprinse între 0 şi 232-1, iar tipul de
date DQ, atunci când plaja de valori este cuprinsă între 0 şi 264-1. Organizarea
datelor cu semn determină identificarea corectă a intervalelor. Astfel, se va defini o
variabilă de tip DB dacă plaja de valori este cuprinsă între –128 şi 127. Se
utilizează tipul DW pentru plaja de valori –32768 şi 32767. Pentru descriptorul DD
plaja de valori este cuprinsă între –232 şi 2 31 –1, iar pentru tipul DQ plaja este
definită prin -263 şi 2263 –1.
Când se alege un descriptor de tip, pentru omogenitate se va avea în vedere
ca rezultatele operaţiilor să aparţină aceluiaşi tip pentru a evita operaţiile de
conversie.
471

7.2 Masive unidimensionale

Masivele unidimensionale se definesc prin:

nume tip n dup (val_1, val_2,…,val_m)

unde:
nume - identificator construit după regulile limbajului;
tip - DB, DD, DQ, DT sau un tip definit de utilizator;
n - numărul de componente ale masivului;
val_j - valoarea obţinută după evaluarea unei expresii cu poziţia j din lista
dacă sunt iniţializate componentele masivului.

În secvenţa:

x db 10 dup (‘a’)
y dw 20 dup (0)
z dd 40 dup (?)
w db 5 dup (3, 4, 5)
u dw 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

se alocă memorie pentru:


 masivul unidimensional x având elemente de un bait, în număr de zece,
fiecare fiind iniţializat cu constanta caracter ‘a’;
 masivul de 20 componente, numit y; fiecare componentă este iniţializată
cu zero; zona de memorie alocată are o lungime de 40 baiţi;
 z, masiv cu 40 componente, care ocupă 160 baiţi, fără iniţializare;
 masivul w, care are 5×3 componente, iniţializate cu w0 = 3, w1 = 4, w2 =
5, w3 = 3, w4 = 5, w5 = 5,…, w12 = 3, w13 = 4, w14 = 5; masivul w ocupă
15 baiţi;
 masivul numit u, având 10 componente, iniţializate una după alta;
componentele sunt definite ca variabile elementare distincte, cu acelaşi
descriptor de tip, DW.

7.3 Masive bidimensionale

Se definesc prin compunerea vectorilor de vectori astfel:

nume tip n dup (m dup (val1-1, val1-2,…,val-k))

unde:
nume - identificator cu care se referă matricea;
tip - descriptor de tip DB, DW, DD, DQ, DT;
n - numărul de linii ale matricei;
472

m - numărul de coloane ale matricei;


val-j - valoarea cu care se evaluează o anumită componentă.
Operatorul DUP este inclus în mod repetat, permiţând generalizarea ce
conduce la definirea de masive de masive multidimensionale.
Secvenţa:
a dw 10 dup (10 dup (0))
b db 3 dup (3 dup (8), (19))
c dd 2 dup (7, 4 dup (3))
d dw 4 dup (1, 2, 3, 4)
e dd 4 dup (5, 2 dup (0), 9)

defineşte matricele următoare:


a – matrice cu 10 linii şi l0 coloane, având toate elementele zero;
b – matrice cu 3 linii şi 4 coloane iniţializată astfel:
8 8 8 19
8 8 8 19
8 8 8 19
c – matrice cu două linii şi 5 coloane iniţializată astfel:

7 3 3 3 3
7 3 3 3 3

d – matrice cu 4 linii identic iniţializate conducând la:

1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4

e – matrice cu 4 linii şi 4 coloane, iniţializată astfel:

5 0 0 9
5 0 0 9
5 0 0 9
5 0 0 9

Matricele fiind masive care presupun elemente de acelaşi tip, lungimile


zonelor de memorie ocupate se obţin înmulţind numărul de componente cu
lungimea zonei asociată fiecărui tip (elementul de tip DB are lungimea un bait,
elementul de tip DW are lungimea doi baiţi, tipul DD presupune alocare 4 baiţi,
473

tipul DT-10). De exemplu, masivul bidimensional ee, având tipul DD, cu 5 linii şi
5 coloane, va ocupa o zonă de memorie de 100 baiţi (4 × 5 × 5).

7.4 Articolul

Articolul este un conglomerat de membri, care sunt fie date elementare, fie
masive, fie alte articole. Tipurile membrilor sunt diferite.
Un articol se defineşte prin:
nume struc
membrul_1
membrul_2
……
membrul_n
nume ends

Masivele sunt cazuri particulare, în care membrii sunt de acelaşi tip.


Definirea unui articol nu presupune rezervare de zonă de memorie. Pentru a rezerva
zonă de memorie este necesară definirea de variabile de tip articol cu numele
articolului definit.
În secvenţa:
persoana struc
nume db 30 dup (?)
vârsta dw ?
salariu dd ?
nr_copii db ?
reţineri dw ?
persoana ends

este definit tipul de date persoana, care este un articol care are în componenţă şase
membri având tipurile DB, DW, DD, DB, respectiv, DW. Lungimea zonei de
memorie ocupată de o variabilă de tip STRUC este dată de lungimea membrilor
care intră în alcătuirea ei. Astfel, o variabilă de tip STRUC persoana va ocupa
30+2+4+1+2=39 baiţi.
În secvenţa:
pers persoana (?)
mat_pers persoana 10 dup (10 dup (?))
vect_pr persoana 20 dup (?)

se definesc:
 o variabilă cu numele de tip STRUC persoana, ce ocupă 39 baiţi, şi ai
cărei membri se referă cu operatorul . (punct) ca de exemplu, pers.
nume, pers.vârsta, pers.salariu;
474

 un masiv bidimensional, având 10 linii şi 10 coloane, cu elemente de tip


persoana (matrice de articole);
 un vector de articole cu 20 de componente.
În toate cazurile construcţia <?> indică neiniţializarea zonelor de memorie
asociate elementelor de tip persoană.
Pentru iniţializarea valorilor pentru fiecare membru la definire sunt dispuse
valori prefixate sau sunt cuprinse într-o listă de valori respectând concordanţa
dintre tipul membrului şi tipul constantei.
În cazul absenţei unei valori, caracterul poziţional al membrilor impune
păstrarea ordinii prin menţinerea separatorilor (virgula), în lista de valori iniţiale
putând apare separatori consecutivi.
În secvenţa:
maşina struc
tip db 20 dup (?)
culoare db ‘roşie’
vechime dw ?
maşina ends
. . . . . . . .
opel maşina (‘berlina’, ,15)
dacia maşina 2 dup (‘break’, , 0)
diverse maşina 100 dup ( , , 20 )

se defineşte tipul de dată STRUC maşina având un câmp cu valoare prefixată (care
poate fi modificată printr-o iniţializare într-o variabilă dacă respectiva variabilă
impune). Se defineşte variabila OPEL de tip STRUC maşina ale cărei câmpuri sunt
iniţializate după cum urmează: OPEL.nume = ‘berlina’, OPEL.culoare = ‘ROŞIE’,
OPEL.vechime OPEL.vechime=15.
DACIA este un masiv unidimensional cu două componente de tip STRUC
maşina. Prima componentă are membri iniţializaţi astfel: DACIA.nume = ‘brek’,
DACIA.culoare = ‘ROŞIE’, DACIA. vechime=0. A doua componentă (lungimea
unui articol este 20+5+2=27 baiţi) are membri referiţi prin DACIA+27 cu valorile
iniţializate următoare: DACIA+27.nume=’brek’, DACIA+27.culoare= ‘ROŞIE’,
DACIA+27.vechime=0.
Masivul unidimensional numit DIVERSE are 100 componente de tip STRUC
maşina în care membri culoare şi vechime se iniţializează cu ‘ROŞIE’, respectiv,
valoarea 20.

Articole cu câmpuri definite la nivel de biţi

Există variabile care au un număr de valori restrânse şi definirea cu tip DB


înseamnă deja risipă de memorie. În evidenţa populaţiei, mediul din care provine
persoana este rural sau urban (1 sau 0), sexul individului este masculin sau feminin
(1 sau 0), persoana poate avea sau nu copii (1 sau 0), poate fi necăsătorit (00),
475

căsătorit (01), divorţat (10) sau văduv (11). Pregătirea profesională poate fi:
absolvent şcoală generală (001), analfabet (000), absolvent liceu (010), absolvent
şcoală profesională (011), absolvent postliceal (100 ), licenţiat (101), absolvent
colegiu (102), doctor în ştiinţe (110), master in science (111). Persoana poate avea
sau nu carnet de conducere (1 sau 0) poate avea sau nu cazier (1 sau 0). Lucrând la
nivel de câmpuri de biţi cu lungime unu, doi sau trei, se va obţine o importantă
economie de memorie la utilizarea de fişiere pentru colectivităţi foarte mari.
Utilizarea de fişiere inverse vine să mărească viteza de regăsire după combinaţii de
caracteristici date sub forma câmpurilor de biţi.
Articolele definite cu câmpuri de biţi utilizează descriptorul RECORD
astfel:
nume record nume-1:n1, nume-2:n2,…, nume-k:nk
unde: nume reprezintă numele tipului de dată RECORD ce se defineşte;
nume-j - numele câmpului j din structura de câmpuri de biţi;
nj - numărul de biţi care formează câmpul j.

În cazul în care la definire apar construcţii de forma:

nume-j:nj=expresie-j

expresie_j se evaluează şi este corectă în raport cu lungimea câmpului, reprezintând


valoarea ce se atribuie respectivului câmp la alocarea de memorie. Astfel,
definirea:

a record al:2, a2:4=11b, a3:2


aa a 3 dup (?)

construieşte tipul de dată având câmpuri pe biţi repartizaţi după urmează: a1 ocupă
2 biţi, a2 ocupă 4 biţi, iar a3 ocupă 2 biţi.
Se creează vectorul aa cu trei componente, fiecare componentă fiind pe un
bait structurat conform tipului de dată RECORD a. Cele trei elemente ale masivului
unidimensional au iniţializaţi biţii ce corespund câmpului a2 cu valoarea 0011b.
Dacă se lucrează la nivel de cuvânt este importantă poziţionarea câmpurilor
întrucât baiţii rămân unităţi de bază şi câmpurile nu pot fi dispersate în doi baiţi
adiacenţi. Fiecare asamblor poate impune restricţii la lungimea în baiţi a tipului de
dată RECORD. De cele mai multe ori, lungimea câmpurilor n1+n2+…+nk nu poate
depăşi 16 biţi.
Când se lucrează cu astfel de structuri se utilizează operatorul WIDTH care
returnează lungimea ca număr de biţi a unui câmp căruia i-a fost aplicat.
Dacă se construieşte secvenţa:
aaa record b:5, c:1, d:2, e:4, f:2, g:2
bbb aaa (?)
. . . . . . . .
476
mov a1, width bbbb + width bbb.e

instrucţiunea mov are ca efect încărcarea în registrul AL a numărului 9 întrucât


operatorul WIDTH aplicat succesiv câmpurilor b şi e, returnează 5, respectiv, 4,
acestea fiind lungimile cu care au fost definite. Pentru a lucra cu biţii câmpului
trebuie “izolaţi” folosind operatorul MASK. În definirea de mai sus a tipului RECORD
aaa, pentru a putea testa valoarea câmpului c acesta va fi încărcat după ce a fost
“izolat” de restul elementelor care formează primul bait, prin instrucţiunea:

mov h a1, mask bbb.c ; a1:=000000x00


cmp a1,0

Bitul notat x reprezintă câmpul izolat, care face obiectul testării.


Câmpurile “neinteresante” ale variabilei RECORD bbb din primul bait sunt
puse pe zero. Cea mai mică unitate de memorie adresabilă este baitul.
Programatorul nu poate avea acces în mod direct la şiruri de biţi. Operatorii WIDTH
şi MASK permit menţinerea nemodificată a secvenţelor de prelucrare atunci când se
modifică structura de definire a variabilelor RECORD.

Reuniunea de date

Se consideră tipuri de date diferite care trebuie să ocupe o aceeaşi zonă de


memorie. Pentru aceasta se utilizează descriptorul UNION astfel:

nume union
membrul-1
membrul-2
membrul-3
. . . . .
membrul-n
nume ends

unde:
nume - reprezintă un identificator care se asociază tipului de dată UNION;
membrul-j - definire tipul de date j;
Lj - lungimea în baiţi a variabilei de tipul j.
O variabilă de tip UNION nume are o lungime egală cu maximul dintre
lungimile L1, L2, . . ., Ln.
Secvenţa:

salariu union
sala dw 5 dup (0)
salb dw ?
nume db 20 dup ?
salariu ends
477
ss salariu 7 dup ( ? )

defineşte o variabilă UNION salariu a cărei lungime L=max (10,2,20)=20 şi


masivul unidimensional ss cu 7 componente neiniţializate. Modul de realizare a
suprapunerii baiţilor este dat în figura 7.1.

baitul 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 . . 19
sala(i) 0 0 1 1 2 2 3 3 4 4
salb ? ?
nume(i)0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 … 19
Figura 7.1 – Suprapunerea în structură de tip UNION

Cifrele din dreptul poziţiilor baiţilor corespund indicilor masivelor. Astfel,


cu poziţia 9 aparţine şi elementul sala (4) din masivul unidimensional sala, dar şi
elementul nume (9). Elementul salb ocupă numai primii doi baiţi.

7.5 Variabile pointer

Spre deosebire de limbajul C în limbajul de asamblare, nu se fac declarări


speciale pentru variabilele pointer. În limbajul de asamblare toate tipurile de
variabile sunt reduse la zonele de memorie pe care le ocupă. Variabilele pointer
memorează deplasamentele unor variabile sau ale unor etichete definite pentru
instrucţiuni executabile.
Secvenţa:

a db ?
sir db ‘abcdefgh’
c dw ?
e dd 111
addr_a dw offset a
addr_sir dw offset sir
addr_c dw offset c
addr_e dw offset e
addr_x dw ?
. . . . . . . . . . . . .
x: mov addr_x, offset x

defineşte variabilele a, şir, c, e de tipuri diferite şi variabilele addr a, addr_sir,


addr_c, addr_e care sunt iniţializate la definire cu deplasamentele variabilelor, toate
având definirea cu descriptorul DW. Variabila addr x tot de tip DW va fi
iniţializată la asamblarea programului, ea conţine deplasamentul unei instrucţiuni
(deplasarea instrucţiunii faţă de punctul de început al segmentului de cod program).
În programe se definesc vectori de pointeri prin:

vector_ptr dw offset adună


478
dw offset scade
dw offset produs
dw offset rest
dw offset ordine
unde: adună, scade, produs, rest şi ordine sunt nume de proceduri ce pot fi apelate
prin instrucţiunea

call vector_ptr + bx

într-o structură repetitivă, incrementând registrul BX. Faţă de limbajele evoluate,


vectorul de pointeri poate conţine deplasamente atât de proceduri cât şi de variabile
sau de instrucţiuni.

7.6 Variabile enumerative

Dacă o variabilă parcurge un set de valori stabilit, aceste valori se pun în


corespondenţă cu coduri numere întregi în ordine crescătoare. Programatorul
utilizează valorile, (uneori nenumerice) dar în program sunt vehiculate codurile.
Definiţia:

nume enum val_1, val_2, val_3, . . . ., val_n

unde:
nume - tipul de date enum definit de programator;
val_j - valoarea j pe care o poate avea o variabilă de acest tip, care se pune
în corespondenţă cu j –1;
De exemplu, în secvenţa:

culoare enum roşu, alb, verde, galben


zile enum luni, marţi, miercuri, joi, vineri, sâmbăta
calificat enum satisfăcător, bine, excelent
boolean enum fals, adevărat
unitate enum bucată, litri, metru, tona
a culoare ( ? )
b zile ( ? )
c calificat 5 dup (bine)
d boolean (fals)
e unitate litri, bucăţi, tona

s-au definit tipurile de date culoare enum, zile enum, calificat enum, boolean şi
unitate tot enum. Variabila a este de tip culoare enum, c este un masiv
unidimensional cu 5 componente de tip califcat enum, variabilele d şi e sunt şi
iniţializate.
Lungimea zonei de memorie asociată depinde de lungimea listei val-j şi
poate fi un bait, doi baiţi, patru baiţi. De cele mai multe ori codurile sunt 0, 1, 2, . . .
479

. , 255, pentru că nivelele variabilelor sunt restrânse şi se alocă pentru variabilele


enum un bait.
Dacă se doreşte alocarea altor coduri, acest lucru este posibil utilizând
construcţii de forma val_j = expresie.
Definirea:

preţ enum preţ_mic=1000, preţ_bun=2000, preţ mare=5000

realizează o altă punere în corespondenţă. Dacă nu se mai continuă iniţializare,


valorile sunt puse în corespondenţă cu coduri generate în continuare. De exemplu:

termin enum start=100, continuare, avansare, final


ccc termin ?

pune în evidenţă că variabila ccc de tip termin ENUM poate lua numai valorile
100, 101 şi 102 şi 103.

7.7 Tabele de date

Ca în cazul lucrului cu şiruri de biţi există posibilitatea de a defini articole


cu câmpuri formate din baiţi, cuvinte, cuvinte duble, 8 sau 10 baiţi folosind
descriptorii BYTE, WORD, DWORD, QWORD?. TBYTE SAU PWORD /
FWORD (câmpuri de 6 baiţi).
Construcţia:

nume TABLE num_1: tip_1, num_2: tip_2, ....,num_k: tip_k

defineşte tipul de date tabelă unde:


nume - numele tipului de dată tabelă definit de programator;
num_j - numele membrului j al tabelei;
tip_j - tipul membrului j al tabelei, în cazul în care tipul este nespecificat
se consideră WORD (DWORD dacă se lucrează pe 32 biţi).
În cazul în care se utilizează construcţia:

num_j: tip_j: nr_j

se va asocia membrului num_j un masiv unidimensional cu nr_j componente de


tip_j.
Secvenţa:

articol TABLE nume:BYTE:30, salariu:WORD,reţineri:WORD:4


aaa articol
480

defineşte tipul de dată articol TABLE şi variabila de tip articol, numită aaa, care
ocupă 50 baiţi. Tipul de date articol TABLE conţine masivele unidimensionale
nume şi reţineri.

7.8 Obiecte

Structura de date obiect este implementată şi în limbajele de asamblare.


Aici se văd mai bine implementările mecanismelor şi proprietăţilor specifice
lucrului cu obiecte.
Un obiect este un conglomerat de membri mult mai neomogen ca structura
de tip articol întrucât acceptă ca membri proceduri sau pointeri spre proceduri.
Înainte de a da regula generală de definire a unei structuri de dată obiect, se va
considera exemplul următor de definire obiect.
Pentru rezolvarea unei probleme sunt necesare procedurile:
 suma – procedură NEAR destinată însumării elementelor unui vector;
 alege – procedura FAR destinată alegerii elementului minim dintr-un
masiv bidimensional;
 calcul – procedură FAR pentru calculul expresiei A/B unde A este suma
returnată de prima procedură, iar B este minimul returnat de procedura
alege.
Datele cu care se vehiculează sunt:
vector dw 100 dup ( ? )
matrice dd 10 dup ( 20 )
total dw 0
minim dd ?
raport dd ?

Definirea obiectului de tip calcul_min trebuie să includă într-o tabelă


metodele (procedurile asociate) şi separat variabilele cu care se va lucra.
Pointerii spre procedurile NEAR se definesc WORD, iar pointerii
procedurilor FAR se definesc DWORD.
Tabela metodelor conţine pentru fiecare metodă numele variabilei pointer
asociată procedurii, tipul şi numele procedurii.
Obiectul calcul_min se defineşte astfel:

calcul_min struc global method


ptr_suma:word=suma
ptr_alege:dword=alege
ptr_calcul:dword=calcul
vector dw 100 dup (?)
matrice dd 10 dup (20 dup (?))
total dw 0
minim dd ?
raport dd ?
481
calcul_min ends

În general, pentru a defini complet un obiect în lista metodelor se vor


include procedurile constructor şi destructor care returnează tipul FAR, necesitând
descriptorul DWORD.
Pentru metode se utilizează atributele GLOBAL (pentru a da caracter global
tabelei de metode) virtuale NEAR şi FAR (pentru a determina conversiile specifice
lucrului cu 16, respectiv, 32 biţi). Aceste atribute vor fi numite modificatori.
În programarea orientată obiect moştenirea este o proprietate importantă,
care presupune un obiect de bază sau obiect părinte. Legătura între obiectul de bază
şi obiectul descendent se efectuează specificând la definirea acestuia din urmă,
numele părintelui.
Toţi operanzii cu care se vehiculează într-un obiect se definesc sub forma
unor membri dintr-un articol.
Pentru definirea unui obiect se foloseşte definirea:

nume struc modificator obiect_părinte method


ptr-1: tip-1=nume-proc-1
ptr-2: tip-2=nume_proc-2
. . . . . .
ptr-n:tip-n=nume_proc-n
membrul-1
membrul-2
. . . . . . . .
membrul-k
nume ends

Definirea unei variabile obiect se efectuează ca şi în cazul articolelor, însă


prin utilizarea constructorilor se face alocarea şi iniţializarea acestor structuri
complexe, care preiau adresele de început ale instrucţiunilor executabile din
procedurile membre.
Apelarea metodelor definite static sau virtual necesită o serie de informaţii
care să permită localizarea segmentului unde se află variabila obiect din care
metoda este membru, numele pointerului care referă metoda precum şi cuvintele
cheie CALL şi METHOD.
Bibliotecile standard conţin definiri de obiecte pentru lucrul cu liste, cozi,
stive şi arbori binari. Metodele vizează operaţiile uzuale cu structurile de date
dinamice, precum: creare, inserare, traversare şi ştergere.
Diversitatea tipurilor de structuri de date este dată de modul în care este
construit asamblorul. De aceea, este foarte important ca înainte de al lucra cu un
asamblor să se vadă care sunt tipurile de date şi ce structuri de date pot fi utilizate
şi cum. Nu toate asambloarele operează cu obiecte, cu tipuri de date sinonime
(typedef) cu tabele sau cu date de tip ENUM. În toate cazurile este necesar să se
urmărească modul de definire a tipului, definirea variabilelor de tipul respectiv,
482

iniţializarea lor la definire şi cum se referă în program variabilele sau componente


ale acestora.

8
IMPLEMENTAREA STRUCTURILOR
FUNDAMENTALE
8.1 Programarea structurată

Programarea structurată trebuie privită ca o modalitate de disciplinare a


programatorilor şi de introducere a unui stil unic în scrierea de texte sursă.
Eliminarea instrucţiunii GO TO din programe este numai aparentă deoarece
apelurile de proceduri, instrucţiunile PERFORM, conţin în mod mascat instrucţiuni
de salt necondiţionat în modulele obiect rezultate la compilare.
Limbajele de asamblare conţin dezvoltări care implementează aproape
perfect cerinţele programării structurate. La nivel de bază implementarea
structurilor fundamentale este o traducere cu posibilităţile specifice ale schemelor
asociate acestor structuri.
Obiectivul implementării structurilor folosind construcţii tipizate este de a
obţine programe lizibile, cu un nivel de liniaritate mai ridicat şi mai simple, chiar
dacă lungimea lor ca număr de instrucţiuni este mare.

8.2 Structura liniară

Structura liniară apare ca o secvenţă de instrucţiuni ce nu conţine salturi


condiţionate sau necondiţionate, instrucţiuni de revenire în procedura apelatoare.
Deşi instrucţiunea CALL este însoţită şi de un salt necondiţionat, ca factor de
liniarizare a programelor se include în mulţimea instrucţiunilor care contribuie la
realizarea de structuri liniare.
Structura liniară conţine instrucţiuni care se execută una după cealaltă,
exact în ordinea în care apar în secvenţă.
Secvenţa:
mov ax,0
add ax, b ; ax:=ax+b
add ax, c ; ax:=ax+c
sub ax, d ; ax:=ax-d
mul ax, e ; dx:ax:=ax × e
mov prod, ax ; prod:=ax
483
mov prod+2,dx ; prod+2:=dx

reprezintă un exemplu tipic de structură liniară.


Secvenţa:

cld
mov cx, OFFSET VAR
call prel
mov x, dx
mov y, c1
xor cx, cx
call prel
mov ah, 9
mov dx, OFSET text2
int 21h
int 11h

corespunde de asemeni unei structuri liniare, instrucţiunile CALL şi INT însemnând


mai întâi integrarea unor secvenţe de instrucţiuni din proceduri, instrucţiunile de
salt spre ele fiind numai un mijloc.
În plus, ele se traduc prin execută procedura prel, respectiv, execută
întreruperea 21h sau 11h, ca operaţii mai complexe, tot aşa cum se execută un
transfer prin instrucţiunea mov sau o poziţionare a indicatorului de condiţie DF
prin instrucţiunea CLD.

8.3 Structură alternativă

În limbajele evoluate, structura alternativă este asociată instrucţiunii IF –


THEN – ELSE.
Prin convenţie, implementarea structurii alternative presupune evaluarea
unei expresii, poziţionarea indicatorilor de condiţie sau efectuarea unei comparări
cu poziţionarea de indicatori de condiţie.
Dacă este adevărată condiţia se execută secvenţa etichetată “adevărat”, iar
în caz contrar se execută secvenţa etichetată “fals”.
Compararea se realizează prin instrucţiunile CMP, CMPSB, CMPSW.
Poziţionarea indicatorilor se efectuează şi prin operaţii de scădere pentru a compara
sau prin alte operaţii, excepţia făcând instrucţiunile MOV, CBW, CWD, LEA, CALL,
ESC, HLT, JXX, LDS, LOCK, LOOP, LES, NOP, CUT, POP, PUSH, RET,
STOS, WAIT, XCHG, XALT.
Secvenţa:

mov ax, a
cmp ax, b
jg adevărat
fals:
mov maxim, b
484
jmp final
adevărat :
mov maxim, ax
final:
nop

pune în evidenţă faptul că secvenţa corespunzătoare condiţiei verificate se


află după secvenţa etichetată “fals”. Pentru a aduce secvenţa etichetată “adevărat”
cât mai aproape de instrucţiunea care poziţionează indicatorii de condiţie ca în
limbajele evoluate pentru IF, se schimbă modul de efectuare a testelor.
Secvenţa:

mov ax, a
cmp ax, b
jle fals
adevărat:
mov maxim, ax
jmp final
fals:
mov maxim, b
final:
nop

este apropiată de instrucţiunea:

IF(a>b)
maxim:=a
ELSE
maxim:=b;

În cazul în care în program se cere implementarea structurii pseudo-


alternative IF – THEN, este preferabil să se modifice de la început testul
indicatorilor de condiţie pentru a obţine o construcţie naturală, fără instrucţiuni de
salt necondiţionat introduse în plus.
Secvenţa:

cmp ax,0
jge final
mov ah, 9
mov dx, OFFSET text
int 21h ;afişare text
final:
mov ah, 4ch
int 21h

este preferată secvenţei:


485
cmp ax, 0
jl negativ
jmp final
negativ:
mov ah, 9
mov dx, OFFSET text
int 21h
final:
mov ah, 4ch
int 21h

care are două instrucţiuni de salt consecutive, greu de acceptat în programe scrise
în limbaj de asamblare eficiente.
Compunerea structurilor alternative impune realizarea unei liniarizări a
secvenţelor fără a introduce multe instrucţiuni de salt.
Secvenţei:

IF (condiţie_1) THEN
IF (condiţie_2) THEN
secvenţa_1
ELSE
secvenţa_2
ELSE
IF (condiţie_3) THEN
secvenţa_3
ELSE
secvenţa_4;

scrisă într-un limbaj evoluat, îi va corespunde liniarizarea:

condiţie_1
salt fals_1
adevărat_1:
condiţie_2
salt fals_2
secvenţa_1
salt final_1
fals_2:
secvenţa_2
salt final_2
fals_1:
condiţie_3
salt fals_3
adevărat_3:
secvenţa_3
salt final_3
fals_3:
secvenţa_4
final_1:
486
nop
final_2:
nop
final_3:
nop

Dacă se doreşte o apropiere de limbaje evoluate se introduc comentarii în


care apar cuvintele IF, THEN, ELSE pe câte o linie a textului sursă din programul
assembler.
Unele dintre etichete pot lipsi, cum secvenţele de instrucţiuni consecutive
NOP pot fi reduse la o singură apariţie. Se va urmări ca regulă poziţionarea
secvenţelor aşa fel încât instrucţiunile de salt să refere instrucţiuni ce vor fi scrise
în continuare.

8.4 Structura repetitivă standard

Se consideră o variabilă de control care ia valorile n, n-1, n-2,…, 3, 2, 1, 0


şi o secvenţă care se repetă atât timp cât variabila de control este diferită de zero.
Testul asupra variabilei de control urmează decrementării acesteia, iar secvenţă de
repetat precede decrementarea variabilei de control (figura .8.1)

cx:=n

secvenţa
de repetat

cx:=cx-1

nu
cx=0

da

Figura 8.1- Structură repetitivă condiţionată posterior


487

Într-un program se implementează această structură standard, condiţionată


posterior cu instrucţiunea loop. Instrucţiunea loop decrementează registrul CX, îl
testează şi efectuează un salt necondiţionat la instrucţiunea a cărei etichetă este
indicată pe aceeaşi linie cu ea.
Secvenţa:

xor si, si
xor di, di
xor ax, ax
mov cx, 15
ciclu:
add ax, x [si]
add si, 2
loop ciclu
mov suma, ax

exemplifică utilizarea structurii repetitive standard pentru însumarea primelor


cincisprezece elemente ale unui masiv unidimensional.
Dacă apare problema însumării elementelor fiecărei linii dintr-un masiv
bidimensional, separat, este necesară includerea unei structuri repetitive standard
într-o structură repetitivă standard, ca în secvenţa:

xor bx, bx
mov cx, NR_LINII
ciclul1:
xor ax, ax
xor si, si
push cx
mov ax, NR_COL
ciclul2:
add ax, mat [bx][si]
add si,2
loop ciclul2
mov sum [di], ax
add di, 2
add bx, NR_COL * 2
pop cx
loop ciclul1

Registrul BX este folosit ca bază, registrul CX are dublu rol (contorizează


numărul de elemente de pe linia matricei şi, respectiv, numărul de linii al matricei).
Trecerea de la o linie la alta se asigură prin modificarea registrului BX. Registrele
index sunt SI (pentru accesarea elementelor de pe coloanele matricei) şi DI (pentru
accesarea elementelor vectorului de sume care sunt calculate.
S-au definit constantele simbolice NR_LIN şi NR_COL pentru a specifica
câte linii şi câte coloane au matricea.
488

iniţializări

expresia
condiţie

secvenţa de
repetat

modificare
operanzi din
expresia
condiţie

Structura repetitivă condiţionată anterior este definită de secvenţa de


schemă logică dată în figura 8.2.

Figura 8.2 – Structura repetitivă condiţionată anterior

În limbajul de asamblare lipseşte instrucţiunea care implementează direct


această structură. Vor exista două instrucţiuni de salt, una pentru întreruperea
repetărilor şi alta, la sfârşitul secvenţei de repetat care va relua evaluarea expresiei
condiţie şi testarea ei. Implementarea este necesară în cazul în care nu se poate
obţine o transformare a structurii repetitive într-o structură standard. De exemplu,
pentru însumarea elementelor dintr-un vector până când suma depăşeşte 1000 sau
sunt epuizate elementele vectorului, în secvenţa:
mov ax, 0
mov si, 0
mov cx, 0
489
ciclu:
cmp ax, 1000
jg final
cmp cx, n
je final
add ax, x[si]
add si, 2
inc cx
jmp ciclu
final:
mov suma, ax
mov număr, cx

cele două teste conduc spre instrucţiunea cu eticheta final, iar asigurarea repetării
este dată de instrucţiunea jmp ciclu. Controlul numărului de repetări se asigură
atât prin contorul CX cât şi prin valoarea registrului AX. Dacă testul este efectuat
asupra registrului CX şi în secvenţa repetitivă nu se modifică în mod corespunzător
(dacă se porneşte de la zero are loc incrementarea, iar dacă se porneşte de la limita
superioară a intervalului are loc decrementarea) efectul este ciclarea infinită.
În toate cazurile se urmăreşte stabilirea corectă a numărului de repetări.
Testarea incorectă a limitei intervalului va conduce fie la neincluderea în calcule a
unor elemente (primul element dacă se va începe cu limita interioară unu), fie la
includerea unui element din afara intervalului (dacă se începe cu elementul zero şi
se numără n elemente în loc de n-1).

8.5 Structura repetitivă condiţionată posterior

Forma nestandard este utilizată când numărul de repetări e dependent de


valoarea unei expresii sau de semnul acesteia sau când variabila de control
generează termenii unei progresii, alta decât progresia aritmetică cu primul termen
n, al doilea termen n-1, iar ultimii termeni nu sunt 3,2,1,0.
Se utilizează condiţionarea posterioară pentru a nu mai repeta unele
instrucţiuni. De exemplu, pentru afişarea unor texte introduse de la terminal se
procedează astfel:
 se afişează mesajul: “INTRODUCEŢI TEXTUL:”;
 se introduce textul de către utilizator;
 se afişează mesajul: “DORIŢI SĂ CONTINUAŢI? (d/n)”;
 se tastează “d” sau “n” şi dialogul continuă sau e terminat.
Dacă se utilizează structura repetitivă condiţionată anterior este necesar ca
înainte de testul de continuare, în mod artificial să se iniţializeze variabila
RĂSPUNS (definită RĂSPUNS EQU al) cu constanta “n”. Structura repetitivă
condiţionată posterior evită acest impediment, aşa cum se poate vedea în secvenţa:
ciclu:
mov ah,9
490
mov dx, OFFSET textl ;text invitaţie
int 21h ;text şir caractere
mov ah, 0ah
mov dx, OFFSET text2 ;text introdus
int 21h
mov ah,9
mov dx, OFFSET text3 ;text de continuare
int 21h
mov ah,1 ;funcţia de
int 21h ;introducere caractere:
cmp a1,”d”
jz ciclu
cmp a1, “D”
jz ciclu
mov ah, 4Ch
int 21h

S-a optat pe posibilitatea de a considera corectă tastarea pentru continuare a


caracterului “d” sau a lui “D”. Variabila textl este iniţializată cu şirul de caractere
“INTRODUCEŢI TEXTUL:”, variabila text3 este iniţializată cu şirul “DORIŢI SĂ
CONTINUAŢI? (d/n)”, iar variabila text2 este definită prin DB 100 DUP (?) ceea
ce înseamnă că textele introduse în cadrul dialogului nu pot depăşi 100 de
caractere. Se are în vedere posibilitatea de a defini şi caracterele de control care să
asigure finalul şirurilor şi returul de car.

8.6 Structura alternativă multiplă

Are corespondent în limbajul C instrucţiunea switch şi în limbajul


PASCAL instrucţiunea case. În limbajele FORTRAN şi COBOL se utilizează
forme generale ale instrucţiunii GO TO cu mai multe etichete şi cu o variabilă de
indicare a etichetei selectate, prin poziţie. Se consideră o variabilă de control care
poate lua una din valorile VAL1,….,VALn, pentru fiecare din valori se ia câte o
secvenţă de instrucţiuni, respectiv, secvenţa_1, secvenţa_2,…,secvenţa_n.
Dacă variabila de control are o valoare diferită de cele specificate se va
executa secvenţa-0. Fiecare secvenţă are o etichetă de început şi se termină cu o
instrucţiune de salt necondiţionat spre un punct comun de continuare a
programului. Eticheta de început a secvenţei I este etich_i, iar eticheta punctului de
continuare este etich_m. Variabila de control are numele var. Structura alternativă
multiplă va fi implementată prin construcţia:

cmp var, VALI


je etich_1
cmp var, VAL2
je etich_2

cmp var, VAL_n
491
jz etich_n
etich_0:
secvenţa_0
jmp etich_m
etich_1
secvenţa_1
jmp etich_m

etich_n:
etiche
secvenţa_n
etich_m:
nop

Structura alternativă multiplă este caracteristică meniurilor în care


utilizatorul poate tasta un caracter îi permite să selecteze o anumită opţiune. Dacă,
de exemplu se cere evaluarea expresiei:

a  b daca x  1
a  b daca x  2

a * b daca x  3
e
b  1 daca x  4
a  1 daca x  5

0 in celelalte cazuri

se definesc şi se iniţializează variabilele a şi b şi se defineşte variabila e.


Diversitatea expresiilor conduce la imposibilitatea includerii unei structuri
repetitive în care x să fie variabilă de control.
Secvenţa:

cmp x,1
je aduna
cmp x,2
je scade
cmp x, 3
je produs
cmp x, 4
je decrement
cmp x,5
je increment
zero:
mov e, 0
jmp final
aduna:
492
mov ax, a
add ax, b
mov e, ax
jmp final
scade:
mov ax, a
sub ax, b
mov e, ax
jmp final
produs:
mov ax, a
mov bx, b
mul bx
mov e, ax
jmp final
decrement:
mov ax, a
dec ax
mov e, ax
final:
nop

implementează structura alternativă multiplă. Se poate vedea că s-a obţinut o


construcţie uşor de interpretat, în care variabilele a şi b nu sunt modificate. Printr-o
altă concepţie, instrucţiunea mov e, ax poate fi integrată secvenţei etichetate final.
O altă formă de implementare a structurii alternative multiple este legată de
includerea secvenţelor în vecinătatea instrucţiunilor de test, selecţiile efectuându-se
din aproape în aproape. Evaluarea expresiei e se realizează prin secvenţa:

cmp x, 1
jne salt_1
adună:
mov ax, a
add ax, b
jmp final
salt_1:
cmp ax, 2
jne salt_2
scade:
mov ax, a
sub ax, b
jmp final
salt_2:
cmp x, 3
jne salt_3
produs:
mov ax, a
mul b1
jmp final
salt_3:
493
cmp x, 4
jne salt_4
decrement:
mov ax, b
dec ax
jmp final
salt_4:
cmp x ,5
jne salt_5
increment:
mov ax, a
inc ax
jmp final
salt_5:
mov ax, 0
final:
mov e, ax

Toate structurile descrise pot fi îmbunătăţite sau ignorate dacă se urmăreşte


construirea de secvenţe cât mai concentrate. Totuşi, pentru realizarea unui stil de
programe specific lucrului în echipă sunt preferate convenţii de tipul celor
prezentate ca implementări ale structurilor fundamentale de programe. Designul
limbajului de programare ia în considerare şi acurateţea limbajului de a reflecta
proprietăţile structurate.

9
ARITMETICI BINARE

9.1 Aritmetica binară pe 8 biţi

Aritmetica pe 8 biţi presupune definirea operanzilor sursă ai expresiilor cu


descriptorul DB.
Pentru efectuarea adunării se utilizează instrucţiunea add. De exemplu,
pentru evaluarea expresiei:

e = a + b

se utilizează secvenţa:
494
a db 20
b db 13
e db ?

mov al, a
add al, b
mov e, al

Regiştrii care participă la aritmetica pe 8 biţi sunt AH, AL, BH, BL, CH, CL, DH
şi DL.

Iniţializarea cu zero a unui registru se efectuează prin instrucţiuni precum:

mov ah, 0
sub bl, bl

xor cl, cl
mov ch, cl

Scăderea pe 8 biţi se efectuează cu instrucţiunea sub. Secvenţa:

x db 25
y db 7
z db ?

mov ch, x
mov dl, y
sub ch, dl
mov z, ch

evaluează expresia:

z=x-y

Înmulţirea pe 8 biţi utilizează instrucţiunea mul. Deînmulţitul se


memorează în registrul AL, iar rezultatul se va găsi în registrul AH : AL.
Secvenţa:

a db 7
b db 5
c db 8
e dw ?
f dw ?
g dw ?

mov al, a
mov bl, b
mul bl
495
mov e, ax
mov al, a
mul c
mov f, ax
mov al, a
mul 3
mov g, ax

evaluează expresiile :

e = a * b
f = a * c
g = 3 * a

Instrucţiunea mul realizează operaţia de înmulţire considerând operanzii ca


numere fără semn (foloseşte bitul de semn ca făcând parte din numărul respectiv şi
nu ca semnul acestuia). Atunci când operatorii sunt consideraţi ca având semn (din
8 biţi, cel mai semnificativ arată semnul) trebuie să se folosească instrucţiunea
imul, aceasta folosind bitul de semn în mod corespunzător. Rezultatul este
considerat număr cu semn. Modul de folosire al acesteia este identic cu cel al
instrucţiunii mul.
Operaţia de împărţire utilizează instrucţiunea div. Secvenţa:

a db 32
b db 4
cat db ?
rest db ?

mov al, a
cbw
div b
mov cat, ah
mov rest, al

evaluează expresia:

cat = [a/b]
rest = a – cat * b

unde parantezele [ ] reprezintă operatorul de extragere a părţii întregi.


Pentru efectuarea împărţirii este obligatoriu ca deîmpărţitul să se afle în
registrul AX, după efectuarea conversiei de la dată de tip DB la dată de tip DW. Câtul
împărţirii se află în registrul AH. Restul împărţirii se află în registrul AL.
Împărţitorul se referă fie ca nume de variabilă:

div b
496

fie ca registru:
div cl
sau

div dh
sau

div bl

fie sub formă de operand imediat:

div 7
sau
div 2

În mod asemănător înmulţirii, împărţirea cu semn nu se mai realizează cu


instrucţiunea div, ci cu idiv. Aceasta recunoaşte bitul de semn şi îl foloseşte
corespunzător, într-o sintaxă identică cu cea a instrucţiunii div.

9.2 Aritmetica binară pe 16 biţi

Aritmetica pe 16 biţi presupune definirea operanzilor sursă ai expresiilor cu


descriptorul DW.
Pentru efectuarea adunării se utilizează instrucţiunea add. De exemplu,
pentru evaluarea expresiei:

e=a+b
se utilizează secvenţa:

a dw 20
b dw 13
e dw ?

mov ax, a
add ax, b
mov e, ax

Regiştrii care participă la aritmetica pe 16 biţi sunt AX, BX, CX ŞI DX

Iniţializarea cu zero a unui registru se realizează prin instrucţiuni precum:

mov ax, 0

sub bx, bx
497

xor cx, cx

Scăderea pe 16 biţi se efectuează cu instrucţiunea sub. Secvenţa:

x dw 25
y dw 7
z dw ?

mov cx, x
mov dx, y
sub cx, dx
mov z, cx

evaluează expresia:

z = x-y

Înmulţirea pe 16 biţi utilizează instrucţiunea mul. In timp ce deînmulţitul se


memorează în registrul AX, rezultatul se va găsi în perechea de regiştrii DX:AX.
Secvenţa următoare:

a dw 7
b dw 5
c dw 8
e dd ?
f dd ?
g dd ?

mov ax, a
mov bx, b
mul bx
mov e, ax
mov e+2, dx
mov ax, a
mul c
mov f, ax
mov f+2, dx
mov ax, a
mul 3
mov g, ax
mov g+2, dx

evaluează expresiile :

e = a * b
f = a * c
g = 3 * a
498

Pentru o înmulţire în care operanzii sunt consideraţi ca având semn, se va


folosi, la fel ca şi pe 8 biţi, instrucţiunea imul. Operaţia de împărţire se realizează
prin instrucţiunea div.
Secvenţa:

a dw 32
b dw 4
cat dw ?
rest dw ?

mov ax, a
cwd
div b
mov cat, ax
mov rest, dx

evaluează expresia:
cat = [a/b]
rest = a – cat * b

unde parantezele [ ] reprezintă operatorul de extragere a părţii întregi.


Pentru efectuarea împărţirii deîmpărţitul se află în registrul AX, iar câtul
împărţirii se află în registrul AX. Restul împărţirii se găseşte în registrul DX.
Împărţitorul se referă fie ca nume de variabilă:

div b

fie ca registru:

div cx
sau

div bx

fie sub formă de operand indexat:

div 7
sau

div 2

În cazul în care operanzii sunt cu semn, situaţia se repetă: se va folosi


instrucţiunea idiv.
499

9.3 Aritmetica binară pe 32 biţi

Atunci când se lucrează cu un procesor cu regiştri de până la 16 biţi şi fără


coprocesor, aritmetica binară pe 32 biţi se realizează prin folosirea concomitentă a
doi regiştri pe 16 biţi.
Adunarea pe 32 biţi se face cu ajutorul instrucţiunii adc (adunare cu
transport). Astfel, secvenţa:

dp1 dd A92Fh
dp2 dd 49837h
dpsum dd ?

mov ax, dp1


add ax, dp2
mov dpsum, ax
mov ax, dp1+2
adc ax, dp2+2
mov dpsum+2, ax

evaluează expresia:

dpsum = dp1 + dp2

0 0 0 6 A 9 2 F +
0 0 0 4 9 8 3 7
0 0 0 B 4 1 6 6

sau detaliat:

carry 1+
0 0 0 6+ A92F+
0004 9837

000B 4166

Scăderea pe 32 de biţi se face cu ajutorul instrucţiunii sbb (scăderea cu


împrumut). Secvenţa:

a dd 6A92Fh
b dd 4B837h
c dd ?

mov ax, b
sub ax, a
mov bx, b+2
500
sbb bx, a+2
mov c, ax
mov c+2, bx

evaluează expresia:
c = a - b

0006A92F-
0004B837
0001F0F8

sau detaliat:

0 0 0 6- A92F-
0 0 0 4- B837
carry 1

0001 F0F8

În continuare se prezintă un exemplu de calcul ce foloseşte combinat


adunarea şi scăderea pe 32 de biţi. Astfel, dacă se doreşte calcularea unei expresii
de forma:

W = x + y +24 – z

se foloseşte o secvenţa de cod de genul:

x dd <valoare oarecare>
y dd <valoare oarecare>
z dd <valoare oarecare>
w dd ?

mov ax, x
mov dx, x+2
add ax, y
adc dx, y+2
add ax, 24
adc dx, 0
sbb ax, z
sbb dx, z+2
mov w, ax
mov w+2, dx

Înmulţirea pe 32 de biţi, atunci când doar registre pe 16 biţi sunt


disponibile, se face prin despărţirea fiecărui operand în două numere. Astfel, dacă
501

avem de înmulţit două numere, A şi B, fiecare cu o valoarea ce depăşeşte


capacitatea de memorare a 16 biţi, acestea se pot scrie şi sub forma:

A = a1 216 + a2
B = b1 216 + b2

Prin urmare, înmulţirea acestor două numere se descompune astfel:

a1 216 + a2 X
b1 216 + b2

a1 b2 216 + a2 b2 +
32
a1 b1 2 + a2 b1 216

a1 b1 232 + (a1 b2 + a2 b1 ) 216 + a2 b2

Acestei descompuneri îi corespunde următoarea secvenţă de cod:

mov ax, a
mul b
mov rez, ax
mov cx, dx
mov ax, a + 2
mul b
mov bx, dx
add cx, ax
adc bx, 0
mov ax, a
mul b + 2
add cx, ax
adc bx, dx
mov rez + 2, cx
mov cx, 0
adc cx, 0
mov ax, a + 2
mul b + 2
add ax, bx
adc dx, cx
mov rez + 4, ax
mov rez + 6, dx

Pentru împărţirea pe 32 de biţi nu putem aplica aceeaşi metodă de


descompunere a numerelor pe 32 de biţi în câte două numere pe 16 biţi.
Pentru a împărţi pe A la B, cu aflarea câtului C şi a restului R, recurgem la
o metodă simplă: adunarea repetată a lui B până la depăşirea numărului A. În
pseudocod, această metodă arată astfel:
502

S=B
C=0
cât timp ( S <= A )
{
C=C+1
S=S+B
}
R=A–(S–B)

Această metodă este relativ rapidă, deoarece nu foloseşte decât operaţii de


adunare şi de scădere pe 32 de biţi. În limbaj de asamblare, transpunerea
pseudocodului de mai sus arată astfel:

a dd <valoare>
b dd <valoare>
c dd ?
r dd ?
s dd ?
. . . . . . . . .
mov ax, offset s
mov word ptr [ax], b
mov word ptr [ax+2], b+2
mov ax, offset c
mov word ptr [ax], 0
mov word ptr [ax+2], 1
iar:
cmp32 s, a
jg et
add32 c, 1
add32 s, b
jmp iar
et:
sub32 s, b
mov ax, offset r
mov word ptr [ax], a
mov word ptr [ax+2], a+2
sub32 r, s

unde cmp32, add32, sub32 sunt macrodefiniţii pentru compararea, adunarea şi


scăderea numerelor pe 32 de biţi. Pentru construirea macrodefiniţiilor, add32 si
sub32, s-au descris mai sus blocurile de instrucţiuni, în timp ce pentru cmp32,
codul macrodefiniţiei este următorul:
cmp32 MACRO va1, val2
mov ax, val
cmp ax, val2
jnz iesire
503
mov ax, val + 2
cmp ax, val2 + 2
iesire:
ENDM

Se compară mai întâi părţile mai semnificative ale celor două valori. Numai
dacă acestea sunt egale se trece la compararea parţilor mai puţin semnificative. În
final indicatorii de condiţie vor fi setaţi corespunzător.
Aceste structuri de cod pot fi folosite atunci când nu se poate face uz de un
coprocesor matematic, iar microprocesorul nu posedă regiştri pe mai mult de 16
biţi. Odată cu apariţia microprocesoarelor pe 32 de biţi, se pot folosi registrele
generale EAX, EBX, ECX, EDX. Aceste metode încă prezintă importanţă pentru
operaţii cu numere de peste 64 de biţi, ele putând fi extinse foarte uşor pe orice
număr de biţi, atunci când se lucrează cu numere foarte mari.

10
ARITMETICI ZECIMALE

10.1 Ajustări

Aritmetica zecimală presupune efectuarea operaţiilor bait cu bait în cazul


reprezentării despachetate sau nibble cu nibble pentru reprezentarea împachetată.
Pentru a menţine la nivelul fiecărui bait reprezentări ale cifrelor de la 0 la 9
se impune efectuarea de ajustări care se fac în mod diferit în funcţie de operaţiile
efectuate asupra operanzilor.
De exemplu, ajustarea pentru adunarea zecimal despachetată înseamnă
corectarea fiecărui nibble mai puţin semnificativ a fiecărui octet în cazul unei
depăşiri la adunare. Rezultatul adunării numerelor:

0 1 0 3 0 5 0 8 +
0 6 0 6 0 1 0 0
0 7 0 9 0 6 0 8

nu necesită ajustări pentru că toate cifrele corespund alfabetului pe care se defineşte


reprezentarea zecimal despachetată. În schimb, rezultatul adunării numerelor:

0 7 0 5 0 6 +
504

0 8 0 7 0 9
0 F 0 C 0 F

conţine elemente care nu sunt în alfabetul reprezentării zecimal despachetate (C,


F). Aplicând instrucţiuni de ajustare aaa bait de bait de la dreapta la stânga se
realizează corecţia şi rezultatul acestei adunări va fi: 01060305.
Efectul unei instrucţiuni de ajustare asupra unui bait stocat în AL se exprimă
în pseudocod astfel:

dacă ((AL) & 0Fh)>9 sau (AF)=1 atunci


{
(AL)←(AL)+6
(AH)←(AH)+1
(AF)←1
(CF)←(AF)
(AL)←(AL) & 0Fh
}

Ajustarea pentru adunarea zecimal împachetat realizează corecţia fiecărui


nibble a octetului unde a fost semnalată depăşirea. Astfel pentru adunarea
numerelor:

0 3 7 8 5 4 +
0 7 8 5 8 7
0 A F D D B

aplicând ajustarea bait de bait se va obţine numărul: 11 64 41.


Există următoarele instrucţiuni pentru efectuarea de ajustări:
Pentru reprezentări ale numerelor în format zecimal despachetat:
AAA – (ASCII Adjust for Addition) care face o ajustare a registrului
acumulator pentru adunare (descrisă mai sus);
AAD – (ASCII Adjust for Division): ajustare pentru împărţire. Această
instrucţiune face o înmulţire a registrului AH cu 10, rezultatul este adunat cu
registrul AL, iar rezultatul final este depus în AL, registrul AH fiind iniţializat cu 0.
AAM – (ASCII Adjust for Multiply): ajustare pentru înmulţire. Conţinutul
registrului AH este înlocuit cu câtul împărţirii registrului AL la 10, apoi conţinutul
registrului AL este înlocuit cu restul împărţirii.
AAS – ((ASCII Adjust for Subtraction): ajustare pentru scădere. Modificările
asupra registrului acumulator efectuate de această instrucţiune sunt exemplificate
cu ajutorul limbajului pseudocod:

dacă ((AL) & 0Fh)>9 sau (AF)=1 atunci


505

{
(AL)←(AL)-6
(AH)←(AH)-1
(AF)←1
(CF)←(AF)
(AL)←(AL) & 0Fh
}

Pentru reprezentări ale numerelor în format zecimal împachetat:


DAA – (Decimal Adjust for Addition): ajustare pentru adunare: dacă cel mai
puţin semnificativ nibble al registrului AL este mai mare decât 9 sau dacă AF este
setat pe 1, atunci la registrul AL se adună 6 si AF este setat pe 1. Dacă AL este mai
mare decât 9Fh sau dacă AF este setat pe 1, atunci la registrul AL se adună 60h şi
AF este setat pe 1.
DAS – (Decimal Adjust for Subtraction): ajustare pentru scădere: dacă cel
mai puţin semnificativ nibble al registrului AL este mai mare decât 9 sau dacă AF
este setat pe 1, atunci din registrul AL se scade 6 si AF este setat pe 1. Dacă AL
este mai mare decât 9Fh sau dacă AF este setat pe 1, atunci din registrul AL se
scade 60h şi AF este setat pe 1.

10.2 Adunarea şi scăderea

ADD - Arithmetic Addition (Adunare)

Folosire: ADD dest, src


Flag-uri modificate: AF CF OF PF SF ZF

Adună operandul src la operandul dest şi memorează rezultatul în


operandul dest. Ambii operanzi au reprezentare binară.

Timpi Lungime
Operatori 808x 286 386 486 Bytes
reg,reg 3 2 2 1 2
mem,reg 16+EA 7 7 3 2-4 (W88=24+EA)
reg,mem 9+EA 7 6 2 2-4 (W88=13+EA)
reg,immed 4 3 2 1 3-4
mem,immed 17+EA 7 7 3 3-6 (W88=23+EA)
accum,immed 4 3 2 1 2-3

De exemplu, secvenţa:

termen1 dw 11
termen2 dw 23
506
total dw ?
……………..
mov ax, termen1
add ax, termen2
mov total, ax
……………

realizează adunarea valorii variabilei termen2 la conţinutul registrului AX, în care


fusese mutată valoarea variabilei termen1. Rezultatul adunării este depus în
registrul AX, iar apoi este mutat în variabila total.

SUB – Subtract (scădere)

Folosire: SUB dest, src


Flag-uri modificate: AF CF OF PF SF ZF

Sursa este scăzută din destinaţie şi rezultatul este copiat în destinaţie.

Timpi Lungime
Operatori 808x 286 386 486 Bytes
reg,reg 3 2 2 1 2
mem,reg 16+EA 7 6 3 2-4 (W88=24+EA)
reg,mem 9+EA 7 7 2 2-4 (W88=13+EA)
reg,immed 4 3 2 1 3-4
mem,immed 17+EA 7 7 3 3-6 (W88=25+EA)
accum,immed 4 3 2 1 2-3

Astfel, secvenţa:

termen1 dw 7
termen2 dw 15
dif dw ?
……………
mov ax, termen2
sub ax, termen1
mov dif, ax
……………

realizează scăderea valorii variabilei termen1 din conţinutul registrului AX, în


care fusese mutată valoarea variabilei termen2. Rezultatul scăderii este copiat în
registrul AX, iar apoi este mutat în variabila dif.

10.3 Înmulţirea şi împărţirea

MUL - Unsigned Multiply (înmulţire fără semn)


507

Folosire: MUL src


Flag-uri modificate: CF OF (AF,PF,SF,ZF nedefiniţi)

Înmulţeşte fără semn acumulatorul cu operandul sursă. Dacă operandul sursă


src este o valoare pe un octet, atunci AL este folosit ca deînmulţit şi rezultatul se
memorează în AX. Dacă operandul src este un cuvânt, atunci AX este înmulţit cu
sursa şi DX:AX primeşte rezultatul. Dacă sursa este un dublu-cuvânt atunci EAX
este înmulţit cu sursa şi rezultatul este pus în EDX:EAX.

Timpi Lungime
Operatori 808x 286 386 486 Bytes
reg8 70-77 13 9-14 13-18 2
reg16 118-113 21 9-22 13-26 2
reg32 - - 9-38 13-42 2-4
mem8 (76-83)+EA 16 12-17 13-18 2-4
mem16 (124-139)+EA 24 12-25 13-26 2-4
mem32 - - 12-21 13-42 2-4

Astfel, secvenţa:

factor1 dw 41
factor2 dw 15
produs dd ?
……………..
mov ax, factor1
mul ax, factor2
mov produs, ax
mov produs+2, dx
……………

realizează înmulţirea valorii variabilei factor2 cu conţinutul registrului AX, în


care fusese mutată valoarea variabilei factor 1. Produsul se obţine în registrul
dublu DX:AX. Rezultatul înmulţirii este apoi mutat în variabila produs definită
pe dublu cuvânt.
Această operaţie presupune obligativitatea folosirii registrului AX pentru
deînmulţit şi a registrului dublu DX:AX pentru rezultat. Înmulţitorul se găseşte
într-un registru, într-o zonă de memorie sau este o constantă imediată.

DIV – Divide (împărţire)

Folosire: DIV src


Flag-uri modificate: (AF,CF,OF,PF,SF,ZF nedefiniţi)
508

Acumulatorul este împărţit la sursă binar fără semn. Dacă operatorul sursă
este un octet atunci AX este împărţit de sursă (DX trebuie să fie 0); câtul împărţirii
este pus în AL, iar restul în AH. Dacă operatorul sursă este un cuvânt, atunci
DX:AX este împărţit la sursă; câtul împărţirii este pus în AX, iar restul este pus în
DX.
Timpi Lungime
Operatori 808x 286 386 486 Bytes
reg8 80-90 14 14 16 2
reg16 144-162 22 22 24 2
reg32 - - 38 40 2
mem8 (86-96)+EA 17 17 16 2-4
mem16 (150-168)+EA 25 25 24 2-4 (W88=158-
176+EA)
mem32 - - 41 40 2-4

De exemplu, în secvenţa:
deimpartit dw 41
impartitor dw 15
cat dw ?
rest dw ?
……………..
mov ax, deimpartit
cwd
div impartitor
mov cat, ax
mov rest, dx
……………

se realizează împărţirea valorii variabilei deimpartit aflată în registrul DX:AX la


conţinutul variabilei impartitor. Câtul se obţine în registrul AX, iar restul se
obţine în registrul DX. Rezultatele împărţirii sunt apoi mutate în variabilele cat,
respectiv rest.
Deîmpărţitul este obligatoriu să fie memorat în registrul dublu DX:AX,
extensia semnului se obţine cu instrucţiunea cwd.

10.4 Proceduri de calcul

Necesitatea aritmeticii zecimale este dată de manipularea numerelor foarte


mari în condiţiile asigurării preciziei. Lucrul în aritmetica binară pe doi baiţi
asigură precizie operanzilor care după evaluarea expresiei conduc la un rezultat
cuprins între [-32768…32767].
În tabel sunt prezentate preciziile pentru rezultatele evaluărilor în cazul
celorlaltor tipuri de date.
509

Tabelul 10.1.
1 octet cu semn -128…127
1 octet fără semn 0…255
2 octeţi cu semn -32768…32767
2 octeţi fără semn 0…65535
4 octeţi cu semn -2^31…2^31-1
4 octeţi fără semn 0…2^32-1

Ideea aritmeticii zecimale constă în dezvoltarea unor proceduri de


manipulare a şirurilor de caractere formate din elementele 00h, 01h, 02h, 03h, 04h,
05h, 06h, 07h, 08h, 09h în cazul aritmeticii zecimale despachetate sau din 0000b,
0001b, 0010b, 0011b, 0100b, 0101b, 0110b, 0111b, 1000b, 1001b în cazul
aritmeticii zecimale împachetate.
De la tastatură se iniţializează şirul x cu ‘7245893106’ care obţine imaginea
memorie (hexazecimal):

37 32 34 35 38 39 33 31 30 36 $

x
Figura 10.1 – Imaginea zonei de memorie ocupată de şirul x

folosind codurile ASCII pentru simbolurile cifrice.


Dacă se traversează masivul unidimensional x (de la stânga la dreapta) şi
din fiecare bait se scade constanta 30h (care corespunde caracterului ‘0’) se va
obţine şirul x modificat a cărui imagine este:

07 02 04 05 08 09 03 01 00 06 $

Figura 10.2 – Imaginea zonei de memorie ocupată de şirul x după modificare

Aceasta este forma zecimal despachetată a numărului foarte mare


7245893106 introdus de la tastatură, şi cu care se doreşte găsirea unei modalităţi de
a face calcule.
Interesează faptul că se cunoaşte numele zonei de memorie (x) şi folosind o
procedură de stabilire a lungimii unui şir a cărui delimitator de sfârşit este
caracterul ‘$’ se va cunoaşte câte cifre are numărul. Restricţia lungimii numărului
este dată de cât de mare este spaţiul contiguu de memorie cu care se poate opera
într-un program scris în limbaj de asamblare.
Se observă că dacă un bait conţine o cifră a numărului primii patru biţi
(high nibble) conţin zerouri iar ceilalţi patru biţi (low nibble) conţin combinaţia
corespunzătoare cifrei.
510

Există posibilitatea de a elimina zerourile (din high nibble) şi de a aduce


acolo combinaţia de biţi pentru o cifră. Deci pe un bait se vor reprezenta două cifre
ale numărului. Această reprezentare se numeşte zecimal împachetată.
Zona de memorie ce conţine numărul introdus de la tastatură este (în forma
zecimal împachetată):

72 45 89 31 06 $

Figura 10.3 – Imaginea zonei de memorie ocupată de variabila y

Împachetarea se face de la dreapta la stânga şi dacă numărul de cifre este


impar se completează baitul cel mai din stânga în partea mai semnificativă cu biţi
de 0. Astfel numărul 72541 introdus de la tastatură va ocupa o zonă de memorie a
cu conţinutul:

37 32 35 34 31 $

a
Figura 10.4 – Imaginea zonei de memorie ocupată de variabila a

Transformarea sa în număr zecimal despachetat în zona de memorie b este:

07 02 05 04 01 $

b
Figura 10.5 – Imaginea zonei de memorie ocupată de variabila b

Împachetarea numărului conţinut de zona de memorie b în zona de memorie


c este:

07 25 41 $

c
Figura 10.6 – Imaginea zonei de memorie ocupată de variabila c

În continuare sunt prezentate exemple de proceduri pentru operaţii în


aritmetica zecimală:
.data
buf db 255,255 dup(?)
null equ 0ffh
511
.code

; procedura de citire de la tastatură a unui şir de caractere


cifrice :
; adresa destinaţiei este es:[di]
getnumber proc
cld
mov ah, 0ah
mov dx, seg buf
mov ds, dx
mov dx, offset buf
mov [dx], 255
int 21h
mov si, offset buf
inc si
xor cx, cx
mov cl, byte ptr [si]
inc si
@@01:
lodsb
cmp al, ’0’
jb @@02
cmp al, ’9’
ja @@02
stosb
loop @@01
@@02:
mov byte ptr [di], null
ret
getnumber endp
; procedura de aflare a numărului de octeţi
; adresa şirului în es:[di] şi rezultat în ax
ndigit proc
cld
mov bx, di
mov al, null
xor cx, cx
not cx
repne scasb
mov ax, di
sub ax, bx
dec ax
ret
ndigit endp

; procedura de conversie şir de cifre – zecimal despachetat


; sursa ds:[si], destinaţia es:[di]
asctobcd proc
cld
mov ah, null
@@03:
lodsb
512
cmp al, ah
je @@04
sub al, ’0’
stosb
jmp @@03
@@04:
stosb
ret
asctobcd endp

; procedura de conversie zecimal despachetat – şir de cifre


; sursa ds:[si], destinaţia es:[di]
bcdtoasc proc
cld
mov ah, null
@@05:
lodsb
cmp al, ah
je @@06
add al, ’0’
stosb
jmp @@05
@@06:
stosb
ret
bcdtoasc endp

; procedura de conversie despachetat – împachetat


; sursa ds:[si], destinaţia es:[di]
unpackedtopacked proc
cld
push es
push di
mov di, si
mov ax, ds
mov es, ax
call ndigit
mov cx, ax
pop di
pop es
shr cx, 1
jnc @@07
movsb
@@07:
lodsw
shl ah, 1
shl ah, 1
shl ah, 1
shl ah, 1
or al, ah
stosb
loop @@07
513
movsb
ret
unpackedtopacked endp

; procedura de conversie împachetat – despachetat


; sursa ds:[si], destinaţia es:[di]
packedtounpacked proc
cld
@@07:
lodsb
cmp ah, null
je @@08
mov ah, al
shr ah, 1
shr ah, 1
shr ah, 1
shr ah, 1
and al, 0fh
stosw
jmp @@07
@@08:
stosb
ret
packedtounpacked endp

; procedura de copiere a unui şir terminat cu caracterul


special (null)
; sursa ds:[si], destinaţia es:[di]
bcdcopy proc
cld
@@09:
mov ah, null
lodsb
cmp al, ah
stosb
je @@09
bcdcopy endp

; procedura de redimensionare a unui număr zecimal împachetat


; sursa ds:[si], destinaţia es:[di], dimensiune şir nou în
ax
bcdnorm proc
cld
push ax
push es
push di
mov ax, ds
mov es, ds
mov di, si
mov al, null
xor cx, cx
neg cx
514
repne scasb
mov cx, di
sub cx, si
mov si, di
dec si
pop di
pop es
pop ax
add di, ax
sub ax, cx
std
rep movsb
mov cx, ax
mov al, 0
rep stosb
ret
bcdnorm endp

; procedura de adunare zecimal împachetat


; sursa ds:[si], destinaţia es:[di] (dest=dest+sursa)
; considerăm numerele normalizate la un număr dat de octeţi
dat ;în cx
bcdadd proc
cld
clc
@@10:
lodsb
adc al, byte ptr es:[di]
daa
stosb
loop @@10
ret
bcdadd endp

; procedura de scădere zecimal împachetat


; sursa ds:[si], destinaţia es:[di] (dest=dest–sursa)
; considerăm numerele normalizate la un număr dat de octeţi
dat în cx
bcdsub proc
cld
clc
@@11:
lodsb
abb al, byte ptr es:[di]
das
stosb
loop @@11
ret
bcdsub endp

; exemplu de apelare proceduri


.data
515
s1 db ‘12632’, null ; ascii
s11 db 11 dup(?) ; unpacked bcd
s12 db 11 dup(?) ; packed bcd
s13 db 11 dup(?) ; normalized packed bcd
s2 db 0, 0, 0, 0, 0, 0, 0, 22, 31, 69 ; normalized
packed bcd
.code
start:
mov ax, seg s1
mov es, ax
mov ds, ax
lea si, s1
lea di, s11
call asctobcd
lea si, s11
lea di, s12
call unpackedtopacked
lea si, s12
lea di, s13
mov ax, 10
call bcdnorm
lea di, s13
lea si, s2
mov ax, 10
call bcdadd
end

10.5 Determinarea lungimii rezultatului evaluări unei expresii aritmetice

Se consideră expresia:

e=(a*b+c)/d–a

Pentru evaluarea acestei expresii în aritmetica zecimală este necesară efectuarea


unor calcule privind lungimea zonelor de memorie cu care se lucrează. Operanzii a,
b, c , d, e ocupă zone de memorie, respectiv de lungimile la, lb, lc, ld, le.
Rezultatul înmulţirii a*b necesită o zonă de memorie la = la + lb + 1.
Rezultatul adunării necesită o zonă de memorie de lungime

ly = max { lx, lc } + 1

Rezultatul împărţirii necesită o zonă de memorie de lungime ly = ly - ld iar


rezultatul scăderii necesită o zonă de memorie de lungime le = max { lz, la }.
516

11
ÎNTRERUPERI

11.1 Întreruperi interne şi externe

O întrerupere este un eveniment care duce la întreruperea temporară a


execuţiei unui program, executând o subrutina numită serviciul întreruperii
(Interrupt Service Routine – ISR), după care reia programul oprit din acelaşi punct,
ca şi când nu s-ar fi întâmplat nimic. Întreruperile au fost realizate ca alternativă la
examinarea repetată a perifericelor pentru detectarea unei operaţii de intrare/ieşire
(polling). Astfel, în locul acestei interogări repetate şi costisitoare, perifericele
generează semnale de întrerupere, apelând serviciul corespunzător.
Deşi momentul de declanşare este oarecare, semnalele de întrerupere nu
sunt recunoscute de către procesor decât între doua apeluri de instrucţiuni. Astfel,
dacă se execută o instrucţiune ce necesită multe cicluri maşina (cum este, spre
exemplu, o înmulţire, care poate necesita până la 139 de cicluri procesor), iar un
apel de întrerupere are loc în timpul acesta, el este pus în aşteptare până la
terminarea instrucţiunii. Excepţie de la această regula o fac instrucţiunile cu
repetare, cum sunt instrucţiunile de lucru cu şiruri (de exemplu rep movsb), care
sunt întrerupte.
În cadrul programării procesoarelor din familia 8086, întreruperile se împart
în două clase:
 întreruperi externe
 întreruperi interne
Întreruperile externe au loc în momentul generării unui semnal de către
diferitele dispozitive spre procesor. Pe de altă parte, întreruperile interne sunt
declanşate în două moduri: ca urmare a apelului unei instrucţiuni int sau ca urmare
a unei excepţii generate de o condiţie de eroare (cum ar fi spre exemplu împărţirea
la zero). Întreruperile interne generate prin apeluri int se mai numesc întreruperi
software.

11.2 Modul de funcţionare a întreruperilor

Un serviciu oferit de o întrerupere nu este altceva decât un tip special de


subprogram. Subprogramele implicite pot fi înlocuite cu rutine construite de către
programator. Datorită faptului că aceste servicii nu sunt proceduri obişnuite, câteva
măsuri trebuiesc luate în cadrul lor:
517

 salvarea tuturor regiştrilor la începutul rutinei


 apelul instrucţiunii sti pentru procesarea altor întreruperi în cadrul
serviciului
 restaurarea regiştrilor la sfârşitul rutinei
 apelul iret ca ultimă instrucţiune
Salvarea şi restaurarea regiştrilor folosiţi este necesară datorită incertitudinii
în ceea ce priveşte momentul în care au loc întreruperile externe. Astfel,
întreruperile de acest tip pot avea loc în orice moment, întrerupând execuţia unui
bloc de instrucţiuni, iar modificarea fără restaurare a regiştrilor folosiţi în acest bloc
duce la rezultate catastrofale pentru program. Pe de altă parte, întreruperile
software au un control mai mare relativ la momentul lor de execuţie şi se pot
sustrage de la această regulă.
Dacă se doreşte ca şi alte întreruperi să fie capabile de activare în timpul
execuţie serviciului, întrerupând astfel execuţia acestuia, se realizează apelul
instrucţiunii sti, care are ca efect setarea indicatorului de condiţie IF (interrupt-
enable flag). Dacă nu se realizează acest apel, iar dacă if nu este setat, întreruperile
ce au loc în timpul rezolvării întreruperii curente, nu vor fi recunoscute de către
procesor. Pentru resetarea (punerea pe zero) a acestui flag se foloseşte instrucţiunea
cli. De remarcat, că aceste două instrucţiuni, nu au nici un efect asupra
întreruperilor interne, software sau generate de o condiţie de eroare, ci numai
asupra acceptării sau nu a întreruperilor externe.
Familia de procesoare 8086 are doi pini de intrare de la dispozitivele
generatoare de întreruperi:
 pentru întreruperi mascabile (Maskable Interrupts) – INTR
 pentru întreruperi nemascabile (Nonmaskable Interrupts) – NMI
Majoritatea dispozitivelor ce generează întreruperi folosesc linia INTR
pentru semnalarea evenimentelor. Instrucţiunile cli şi sti afectează întreruperile
ce sosesc pe acest pin: cli realizează o “mascare” a întreruperilor, prevenind
recunoaşterea lor de către procesor, în timp ce sti permite intervenţia acestora în
codul executat de către procesor. Nici una dintre aceste instrucţiuni nu are efect
asupra liniei NMI, pe care circulă semnale importante, ce nu pot fi mascate, cum ar
fi cele ce declanşează execuţia codul în cazul căderii tensiunii. În cadrul arhitecturii
IMB PC originale, linia NMI era folosită pentru erorile de paritate a memorie
RAM, cauzate de defectarea unuia sau mai multor biţi. Arhitecturile actuale oferă
multe alte servicii noi pentru situaţii critice, făcând programarea serviciilor NMI
complicată.
Deşi linia NMI pare de neblocat în sarcina sa de a trimite semnale de
întrerupere către procesor, ea poate fi totuşi blocată prin diferite trucuri ce diferă de
la o arhitectură la alta. Astfel, pentru IBM XT, se poate masca şi debloca această
linie prin scrierea valorii 00h (pentru mascare) şi a valorii 080h (pentru deblocare)
pe portul 0A0h.
518

Pentru întoarcerea în mod corect în programul întrerupt, se va folosi


instrucţiunea iret în locul instrucţiunilor obişnuite de revenire în aplicaţie.
Această instrucţiune, pe lângă sarcina de refacere a punctului de execuţie (refacerea
din stivă a perechii CS:IP), realizează şi sarcini suplimentare specifice
întreruperilor, cum ar fi refacerea flagurilor IF şi TF (trap flag). Pe lângă cele doua
linii de transmitere a întreruperilor mai sus amintite, arhitectura IBM vine în
ajutorul programatorilor cu un cip, numit Intel 8259 Programmable Interrupt
Controller (PIC), care serveşte până la opt dispozitive ce generează întreruperi.
Arhitecturile mai noi au în dotare şi alte cipuri de tip PIC pentru mai multe
dispozitive. Pentru arhitecturile IBM AT cu două cipuri PIC lista întreruperilor
externe se prezintă astfel:

Tabel 11.1.
Nivelul PIC Numarul Intreruperi Dispozitiv
0 08h Timer (ceasul software)
1 09h Tastatura
2 0Ah Slave 8259
3 0Bh Portul serial secundar (COM2)
4 0Ch Protul serial primar (COM1)
5 0Dh Hard Disk (Fix)
6 0Eh Floppy Disk
7 0Fh Imprimanta pe port paralel
8 070h Ceasul hardware
9 071h Master 8259 Nivel 2
10 072h -
11 073h -
12 074h -
13 075h Coprocesorul numeric
14 076h Hard Disk (Fix)
15 077h -
NMI 02h Paritate Memorie
*datorita faptului că întreruperile NMI sunt tot externe, aceasta linie
a fost inclusa in tabel deşi nu este ataşată controller-ului 8259

Prima coloana a tabelului anterior, nivelul PIC, arată prioritatea serviciului:


dacă două întreruperi au loc simultan, cotroller-ul 8259 dă prioritate celei cu acest
număr mai mic.
A doua coloană, numită numărul întreruperii (tipul sau nivelul
întreruperii), identifică ISR-ul (serviciul) ce rulează la declanşarea acesteia. Acesta
este un index folosit de procesor pentru determinarea adresei rutinei, în timp ce
nivelul PIC arată pinul din cadrul cipului 8259. Acest număr se regăseşte şi în
cadrul întreruperilor software, el fiind folosit în instrucţiunile int ca argument.
519

Prin intermediul numărul instrucţiunii, procesorul află un pointer pe patru


baiţi, numit vectorul întreruperii, care punctează rutina de tratare a întreruperii. În
acest pointer, segmentul şi deplasamentul, sunt memorate la adresa mai mica, cu
valori între 0000:0000 si 0000:30FF pentru arhitectura PC AT. În momentul în care
un semnal de întrerupere este generat de unul de dispozitivele periferice, cipul 9259
activează linia INTR, aşteptând încuviinţarea procesorului pentru primirea
întreruperii. La primirea încuviinţării, procesorul primeşte numărul întreruperii şi îl
foloseşte pentru aflarea adresei rutinei de tratare pe care o apelează.
În mod asemănător au loc operaţiile pentru apelul rutinei de tratare a unei
întreruperi interne ca urmare a unei instrucţiuni int sau ca urmare a unei condiţii
de eroare. Atât în cazul întreruperilor externe, cât şi în cel al celor interne,
procesorul realizează următoarele operaţii când primeşte un număr de întrerupere:
 indicatorii de condiţie sunt salvaţi pe stivă
 indicatorii de condiţie IF si TF sunt setaţi pe zero
 regiştrii IP şi CS sunt salvaţi pe stivă
 vectorul întreruperii (adresa serviciului) este copiat în CS:IP
Odată cu iniţializarea regiştrilor CS:IP cu valoarea vectorului întreruperii,
se trece la execuţia rutinei aflată la acea adresă. Programatorul poate modifica unul
sau mai mulţi vectori de întreruperi şi, astfel, să îşi introducă propriile rutine de
tratare. La terminarea unei rutine de tratare, se apelează instrucţiunea iret care
realizează următoarele operaţii:
 regiştrii CS şi IP sunt restauraţi din stivă
 indicatorii de condiţie sunt refăcuţi, de asemenea, din stivă
Prima operaţie este operaţia obişnuită de refacere a adresei de revenire, pe
care şi o instrucţiune obişnuită de retur o realizează. După care urmează refacerea
indicatorilor de condiţie. Se observă că indicatorii de condiţie IF şi TF sunt resetaţi,
la intrarea în rutină, după ce au fost salvate împreună cu celelalte flaguri. Astfel, pe
parcursul execuţiei serviciului ele vor avea valoarea zero, nepermiţând întreruperea
rutinei de alte întreruperi (indicatorul de condiţie IF) şi nici suport de întrerupere a
instrucţiunilor pentru debuggere (indicatorul de condiţie TF – trap flag). Pentru
permiterea întreruperii temporare a serviciului (setarea indicatorului de condiţie IF)
se apelează instrucţiunea sti.

11.3 Tipuri de întreruperi

O primă clasificare a întreruperilor este cea realizată în primul subcapitol:


externe şi interne. Întreruperile se mai pot clasifica si după locul pe care îl ocupă
rutinele de tratare implicite. Astfel, unele rutine de tratare se găsesc în BIOS, în
timp ce altele sunt oferite de sistemul de operare.
În cazul sistemului de operare MS-DOS, pe lângă shell-ul, ce interpretează
comenzile, se găsesc doua straturi numite BDOS (Basic Disk Operating System) şi
BIOS (Basic Input Output System). Pe disc acestea se gasesc în fişierele
520

MSDOS.SYS si IOSYS.SYS. Aceste straturi cuprind rutine de tratare a


întreruperilor, primul oferind rutine din partea sistemului de operare, în timp ce al
doilea strat, BDOS, oferă o legătură spre întreruperile oferite de BIOS. Astfel, o
alta clasificare a întreruperilor, pentru MS-DOS, este:
 întreruperi BIOS
 întreruperi DOS

11.3.1 Întreruperi BIOS

Rolul BIOS-ul este acela de a acţiona ca o interfaţă între software si


hardware, impunând un standard ce trebuie respectat de toţi manufacturierii de
calculatoare. O parte din rutinele BIOS, cum ar fi cele ce realizează testele de
verificare a resurselor hardware (POST – Power On-Line Self Test) se găsesc în
memoria ROM, în timp ce partea de BIOS cuprinsă în fişierul IOSYS.SYS se
încarcă în partea inferioară a memorie RAM. Modul de mapare a memorie este
standardizat, existând mai multe versiuni de BIOS: Award, Phoenix etc. Dacă în
primele versiuni, BIOS-ul oferea întreruperi cu numere cuprinse între 00h şi 1Fh,
arhitecturile moderne oferă seturi extinse de întreruperi, tabelul 11.2.
Numărul serviciilor oferite de BIOS este foarte mare, de aceea în tabel sau
omis multe dintre acestea. După cum se observă, unele dintre acestea oferă un mod
de întrerupere a procesorului de către dispozitivele ataşate la acesta, aşa numitele
IRQ-uri (Interrupt Request), prezentate, în mare majoritate, în tabelul 11.1, în timp
ce altele oferă o modalitate de accesare a dispozitivelor de către programator prin
intermediul instrucţiunii int.
Modul de întrebuinţare a întreruperilor de acces la dispozitive, este comun
atât serviciilor BIOS cât şi celor DOS. Pe lângă numărul de întrerupere, care indică
dispozitivul ce se vrea accesat, programatorul trebuie să specifice şi funcţia pe care
o doreşte realizată. Aceasta se specifică prin registrul ah (câteodată, în funcţie de
serviciu şi funcţie, se foloseşte şi AL, BX şi DX), după cum se observă în exemplu
următor:
;pozitionare cursor:
;pregatire parametrii
mov ah, 02h ;functia 02h
mov bh, 00h ;pagina video
mov dh, 10 ;linia
mov dl, 12 ;coloana
;apel intrerupere
int 10h ;serviciu video
;nu se intoarce nici un rezultat

Acest exemplu poziţionează cursorul pe linia 10, coloana 12, pe prima pagina
video, apelând funcţia 02h a serviciului 10h. Modul de folosire al întreruperilor
521

este acelaşi, ceea ce diferă fiind doar modul de transmitere al parametrilor şi de


primire a rezultatelor, acolo unde cazul.

Tabel 11.2
Număr întrerupere Semnificaţie (serviciu)
00h Divizare la zero
01h Modul single-step (vezi flag tf)
02h Nemascabil (NMI)
03h Breakpoint
04h Depăsire (overflow) (vezi flaguri of si ov)
05h Tipărire ecran
06h Cod de operaţie nevalid
07h Nu există coprocesor matematic
08h IRQ0 Timer
09h IRQ1 Tastatură
0Ah IRQ2 cascade (trecerea pe controllerul 2 de
întreruperi)
0Bh IRQ3 COM 2/4
..... .....
10h Serviciul video
11h Lista echipamentelor
12h Mărimea memoriei convenţionale
13h Disk I/O
14h Port serial
..... .....
40h Vector către funcţiile int 13h referitoare la
dischetă
41h Vectori către structurile referitoare la hard
discuri
43h Tabela de caractere EGA si VGA
46h Vectori către structurile referitoare la hard
discuri
4ah Alarmă utilizator
67h Funcţii ale managerului de memorie expandată
70h IRQ8 Real Time Clock
..... .....
76h IRQ14 hard disk
77h IRQ15 (rezervat)

11.3.2 Întreruperi DOS

Întreruperile oferite de stratul BDOS, oferind în special rutine de acces la


disc, fac invariantă interfaţa în raport cu diferitele particularităţi hardware din
522

modelele de PC-uri oferite de fabricanţi. Deşi mai puţine la număr, întreruperile


DOS oferă un mare număr de funcţii, tabelul 11.3.

Tabel 11.3
Număr întrerupere Semnificaţie (serviciu)
20h* Terminare program
21h Servicii DOS
22h Adresa de revenire din program după terminarea
acestuia
23h Adresa de revenire după terminare cu Control-Break
24h Adresa de revenire după o eroare critică
25h/26h Acces direct la disc
27h* Terminare program cu rămânere rezindent în memorie
28h Procesor neocupat (idle)
29h Acces rapid la ecran
2Eh Executare comandă DOS
2Fh Multiserviciu (DoubleSpace, spooler, control TSR, etc.)
31h Servicii DPMI (folosite sub modul Windows 386Enh)
33h Suport mouse
67h Servicii ale managerului de memorie expandată
(HIMEM.SYS)
*Aceste servicii sunt rămase de la versiunile iniţiale de DOS, existând
servicii DOS mai noi şi mai uşor de folosit care le înlocuiesc.

În continuare se prezintă un exemplu de folosire a serviciului DOS 21h:


mov ah, 09h ;functia de afisare sir
mov dx, sir ;in ds:dx se pune adresa sirului
int 21h ;numarul serviciului

Secvenţa anterioară realizează afişarea unui sir terminat cu caracterul $ prin


apelul funcţiei 09h a serviciului 21h. Acest cod demonstrează modul similar de
folosire al întreruperilor atât pentru stratul BIOS cât şi pentru stratul DOS. Pentru
accesarea resurselor hardware în limbaj de asamblare există trei niveluri de
programare:
 la nivel DOS
 la nivel BIOS
 la nivel de bază.
Aceste modalităţi au fost aşezate în ordinea crescătoare a complexităţii
programării. Astfel, nivelul DOS oferă servicii de un nivel mai înalt decât nivelul
BIOS, în timp ce programarea la nivel de bază este cea mai grea folosind
instrucţiunile in şi out de lucru cu porturi. Această modalitate de accesare a
resurselor necesită cunoştinţe avansate si o documentaţie pe măsură, pe când în
programarea cu întreruperi, cunoscând modul general de utilizare al acestora,
programatorul trebuie doar să aibă la dispoziţie documentaţia pentru fiecare funcţie
523

referitoare la modul de transmitere al parametrilor şi de preluare a rezultatelor. Cu


toate acestea, unele sarcini realizate cu ajutorul întreruperilor necesită cunoştinţe
profunde hardware, precum şi folosirea concomitentă cu instrucţiunile in şi out,
după cum se va observa în exemplu următor.

11.4 Exemplu de folosire al întreruperilor

Folosirea întreruperilor necesită cunoştinţe despre efectele colaterale ale


acestora. Programatorul, în funcţie de întreruperea folosită, trebuie să ştie
mecanismele ce se află în spatele serviciului. Pentru a scrie un program care să nu
afecteze buna funcţionare a sistemului, el trebuie să ţină cont atât de buna
funcţionare a propriului program, luat separat, cât şi de funcţionarea a celorlaltor
programe executate în paralel cu acesta, precum şi a dispozitivelor periferice care
folosesc întreruperi. Toate aceste probleme pe care le are de înfruntat un
programator nu au o forma generală, ele sunt specifice fiecărui tip de întrerupere,
neputându-se pune un diagnostic concret unei erori date decât ţinându-se cont de
întreruperile folosite.
În continuare, pentru exemplificarea problemelor ce apar la folosirea unei
întreruperi, se prezintă un exemplu, devenit clasic, preluat din cartea “Mastering
Turbo Assembler” a lui Tom Swan, cunoscut autor de cărţi de programare. Acest
exemplu, relativ simplu la prima vedere, îşi propune să folosească întreruperea IRQ
0h, cu numărul 08h (Timer), pentru executarea unui cod la intervale regulate de
timp. Acest cod nu face altceva decât să încetinească programul prin adăugarea de
pauze mici, fiind folositor în cazul în care se doreşte depanarea unui program care
se dovedeşte a fi prea rapid pentru observarea operaţiilor pe care le realizează,
depanarea pas-cu-pas fiind o alternativă greoaie de folosit.
În toate calculatoarele compatibile IBM PC, există un timer hardware, care
generează, la intervale egale de timp (de 18.2 ori pe secundă), un semnal de
întrerupere pe linia 0h către controllerul 8259, producând o întrerupere cu numărul
08h, de maximă prioritate. Serviciul cu acest număr are două sarcini. Prima constă
în incrementarea unei variabile pe 32 de biţi, care indică numărul de cicluri ale
timerului de la pornirea calculatorului, folositoare pentru diferite funcţii ce indică
timpul de rulare. A doua sarcină controlează modul de funcţionare al motorului
unităţii de dischetă. Această sarcină este datorată timpului necesar motoraşului
pentru a ajunge la turaţia necesară. Dacă acesta ar fi oprit după fiecare
citire/scriere, între aceste operaţii există timpi morţi necesari rotorului pentru a
ajunge la turaţia optimă, lucru care ar duce la o încetinire mare a operaţiilor cu
discheta. Soluţia a constat în menţinerea în turaţie a motoraşului atâta timp cât un
cronometru, controlat de întreruperea 08h, nu ajunge la zero.
În scrierea unui program care foloseşte întreruperea, trebuie ţinut cont de
aceste sarcini suplimentare. Codul programatorului, care este apelat la intervale
regulate de timp, nu trebuie să dureze mult. În cadrul acestuia nu trebuie să se
524

oprească primirea întreruperilor de către procesor (instrucţiunea cli) pentru mai


mult de 1/18,2 secunde, pentru a nu se afecta buna funcţionare a unor componente
esenţiale ale sistemului. După cum se observă, folosirea unei întreruperi relativ
banale, ridică în mod neaşteptat, pentru necunoscători, probleme mari în
funcţionarea unor periferice şi a unor programe relativ independente.
Pe lângă aceste doua sarcini, serviciul timer execută o întrerupere software
cu numărul 01Ch (User timer interrupt), care dă posibilitatea programatorului de a
executa codul său. Prin instalarea propriei rutine de tratare a întreruperii 01Ch,
aceasta este apelată de aproximativ 18.2 ori pe secundă. Regularitatea apelării
acesteia nu este garantată. După cum s-a afirmat deja, întreruperile nu pot să
întrerupă instrucţiunea curentă. Dacă aceasta necesită multe cicluri procesor,
executarea întreruperii se amână nedorit de mult, astfel încât premisa executării de
18.2 ori pe secundă a codului să fie neadevărată.
În continuare se prezintă codul programului sursă, urmând ca fiecare grup
de instrucţiuni să fie prezentat în amănunt:
;****************************************
%TITLE “Exemplu de folosire a intreruperii 01Ch"
;****************************************
IDEAL
.MODEL small
.STACK 256
delay EQU 0010h ; pauza
BIOSData EQU 040h ; adresa segmentului de
;date BIOS
LowTimer EQU 006Ch ; valoarea timerului (partea mai
; putin semnificativa)
PIC8259 EQU 0020h ; adresa de port a
;cipului 8259 PIC
EOI EQU 0020h valuarea “End of
;interrupt”
DATASEG
codIesire DB 0
timerSeg DW ? ; adresa serviciului initial
timerOfs DW ? ; Int 1Ch ISR

CODESEG
Start:
mov ax, @data ;initializare registru
mov ds, ax ; de date
mov es, ax ; es = ds
mov [word cs:difference],delay ; stabilire pauza

push es ;salvare registru es


mov ax, 351Ch ;obtinerea vectorului de
; intrerupere 1C
int 21h ; apel serviciu DOS
mov [timerSeg], es ; salvare segment
525
mov [timerOfs], bx ; salvare offset
pop es ; restaurare es
push ds ; salvare registru ds
mov ax, 251Ch ; setarea serviciului 1C
push cs ; ds = cs
pop ds
mov dx, offset Incetinire ; adresa de inlocuire
int 21h ; setare adresa
pop ds ; restaurare ds

@@10:
call KeyWaiting
jz @@10 ; asteptare apasare tasta
push ds ; salvare ds
mov ax, 251Ch ; setare vector 1C
mov dx, [timerOfs] ;adresa de segment
initiala
mov ds, [timerSeg] ;offset-ul initial
int 21h ; restaurare serviciu initial
pop ds ; restaurare ds
Exit:
mov ah, 04Ch
mov al, [codIesire]
int 21h

;****************************************
; Incetinire - Procedura apelata de aprox. 18,2/sec
; ****************************************
inProgress DB 0 ; flag
difference DW 0 ; timpul de incetinire

PROC Incetinire

;testare flag inProgress pentru a vedea daca o copie


;anterioara a procedurii ruleaza
cmp [byte cs:inProgress], 0 ; verificare
jne @@99 ; ruleaza,
terminare
; procedura
inc [byte cs:inProgress] ; nu ruleaza,
; incrementare flag

sti ; pot sa aiba loc alte


; intreruperi
push ax ; salvare registrii
; folositi
push ds
push dx

mov al, EOI ; al = end-of-interrupt


out PIC8259, al
526
mov ax, BIOSData ; adresa de date BIOS
mov ds, ax ; folosire ds
mov ax, [word LowTimer] ; valuare timer
@@10:
mov dx, [word LowTimer] ; incarcare valoare
; timer in dx
sub dx, ax ; scadere valoare
; noua - veche
cmp dx, [cs:difference] ; comparare cu
; pauza
jb @@10 ; cicleaza pana la
; depasire
cli ; dezactivare intreruperi
; in timpul restaurarii
dec [byte cs:inProgress] ; flag pe zero
pop dx ; restaurare registri
pop ds
pop ax
@@99:
iret ; retur intrerupere
ENDP Incetinire

PROC KeyWaiting
push ax ; salvare ax
mov ah, 1 ; functia de
; verificare buffer
int 16h ; serviciu tastatura
; BIOS
pop ax ; restaurare ax
ret ; retur
ENDP KeyWaiting

END Start ; sfarsit program

La rularea programului se instalează procedura Incetinire ca serviciu al


întreruperii 1Ch, urmând ca, la apăsarea unei taste, programul să se termine.
Listing-ul începe prin definirea unor simboluri. Pe lângă definirea pauzei (delay),
se definesc o serie de simboluri, care presupun cunoaşterea modului de mapare al
variabilelor BIOS în memoria RAM. Astfel, adresa 40h semnifică începutul zonei
de mapare BIOS, pe când 40h::6Ch este locaţia de memorie unde valoarea curentă
a timerului se află. Urmează segmentul de date, cu definirea unor variabile ce vor fi
folosite pentru reţinerea adresei vechii rutine de tratare a întreruperii 1Ch
(timerSeg: timerOfs).
După liniile de început a segmentului de cod, urmează obţinerea rutinei de
tratare a întreruperii 1Ch. Se foloseşte întreruperea DOS 21h (int 21h), funcţia 35
(registrul ah = 35, registrul al = 1Ch). Vectorul obţinut se salvează în
timerSeg:timerOfs. Urmează setarea ca serviciu a rutinei Incetinere prin funcţia 25
a întreruperii 21h. După stabilirea noului serviciu, se trece la executarea unei bucle
din a cărei repetare se iese prin apăsarea unei taste. Pe parcursul acesteia, deşi
527

aparent nimic nu se întâmplă, procedura Incetinere este apelată în mod repetat de


către serviciul întreruperii 08h (timer). La ieşirea din ciclu, se reface vechea rutină
de tratare a întreruperii 1Ch. Acest lucru este necesar, deoarece, odată cu terminare
programului, adresa, spre care Incetinire puncta, devine invalidă, iar apelul realizat
la această adresă de către serviciul timer duce, inevitabil, la o cădere de sistem.
Se trece acum la rutina de tratare a întreruperii, Incetinire. Deoarece această
rutină poate fi apelată în orice moment, între două apeluri de instrucţiuni din
programul principal, în timpul unui apel către sistemul de operare, sau în timpul
unei alte întreruperi, valoarea segmentelor ES şi DS nu punctează întotdeauna către
segmentele de date. Pentru accesarea unor variabile din cadrul segmentelor de date,
trebuie să fim siguri că aceste registre identifică în mod corect aceste segmente. O
soluţie este executarea unei secvenţe de cod care în mod uzual se găseşte la
începutul programului principal:

push ds
mov ax, @data
mov ds, ax
. . . . .

urmată, la sfârşitul întreruperii, de refacerea segmentului ds:

. . . . .
pop ds
iret

O altă soluţie, mai elegantă şi mai simplă, este declararea variabilelor în


cadru segmentului de cod. Acest lucru se realizează în procedura Incetinire prin:

inProgress DB 0 ; flag
difference DW 0 ; timpul de incetinire

Pentru accesarea variabilelor se indică în mod explicit folosirea registrului


CS, ca în cazul instrucţiunii:
cmp [byte cs:inProgress], 0

Dacă nu se specifică registrul CS, asamblorul consideră ca registru segment


registrul DS, iar adresa de memorie accesată, în cel mai bun caz, dacă este validă,
nu este cea dorită.
După cum arătat deja, întreruperea 08h joacă un rol important în
funcţionarea unui sistem de calcul. Rutina apelată de timer nu trebuie să dureze
mult, iar pe parcursul acesteia celelalte întreruperi nu trebuiesc blocate. Această
situaţie face posibilă apariţia apelului rutinei Incetinire chiar în timpul rulării
acesteia. Nimic nu împiedică procesorul să primească încă o întrerupere 1Ch din
528

partea timerului în timpul execuţiei procedurii Incetinire, dacă aceasta durează prea
mult. Astfel, acestă rutină devine reentrantă. Din păcate scrierea unei funcţii
reentrante ridică probleme sulpimentare, chiar rutinele din cadrul BIOS-ului si
DOS-ului nefiind reentrante. Posibilitatea reapelării procedurii Incetinire poate să
ducă la depăşirea stivei dacă procedura durează prea mult, iar lanţul de reapelării
continuă fără oprire. Mai mult, folosirea unor variabile globale face ca execuţia să
ia o întorsătură neprevăzută, o singură instanţă a acestora fiind folosită la toate
apelurile. După cum se observă folosirea variabilei difference, face imposibilă
folosirea rutinei Incetinire în mod reentrant. Soluţia la această problemă (soluţie pe
care o folosesc şi unele funcţii BIOS/DOS, cum ar fi Print Screen) este folosirea
unui flag (în cazul de mai sus inProgress), care să indice dacă o instanţă a rutinei
este deja apelată:

cmp [byte cs:inProgress], 0

Dacă inProgress este mai mare ca zero, instanţa curentă a apărut ca urmare
a blocării altui apel al rutinei, şi se sare la sfârşitul procedurii. Dacă este zero,
instanţa curentă este singura apelată în acest moment şi se poate continua execuţia
ei. Urmează incrementarea flagului pentru indicarea acestui lucru.
După luarea acestor măsuri, urmează altele: permiterea altor întreruperi (sti)
şi salvarea regiştrilor folosiţi în stivă. Deşi, la prima vedere, instrucţiunea sti pare
suficientă pentru activarea întreruperilor, datorită faptului că întreruperile timerului
sosesc prin intermediul cipului 8259, acest lucru nu este adevărat. Deşi
instrucţiunea sti setează flagul if, permiţând indicarea către procesor a semnalelor
de întrerupere, primirea de către acesta a întreruperilor necesită trimiterea către
portul 8259 (cu valoarea 20h) a unei comenzi end-of-interrupt (EOI):

PIC8259 EQU 0020h ; adresa de port a cipului 8259 PIC


EOI EQU 0020h ; valuarea “End of interrupt”
. . . . . .
mov al, EOI ; al = end-of-interrupt
out PIC8259, al
. . . . . .

Urmează citirea valorii curente a timerului şi folosirea acesteia în cadrul


unei bucle pană la depăşirea pauzei (variabila difference cu valoarea luată din
delay). Deşi la o simplă privire pare că variabila punctată de adresa LowTimer are o
valoarea constantă, ea este actualizată automat de către serviciul timer, iar simpla
recitire a acesteia preia o valoare actualizată.
Ultima parte a procedurii conţine o restaurare a regiştrilor folosiţi, precum
şi o resetare a flagului inProgress, precedate de o mascare necesară a întreruperilor
(instrucţiunea cli). O atenţie deosebită trebuie avută în vedere în legătură cu
variabilele salvate pe stivă atunci când se scrie o rutină de tratare a unei întreruperi.
Singurul registru de segment cu valoare corectă pe tot parcursul execuţiei rutinei
529

este CS, în timp ce ES, DS şi SS au valori incerte, în funcţie de codul în curs de


execuţi înainte de întrerupere. În exemplul anterior se salvează trei regiştri în stivă.
Dar, în care stivă? În stiva proprie DOS-ului, în stiva programului principal sau în
stiva locală altei rutine de tratare a unei întreruperi? Este această stivă suficient de
mare? De obicei, se consideră sigură salvarea unei mici cantităţi de date într-o
astfel de stivă necunoscută. Referinţe de programare prevăd rezervarea pentru
spaţiul de stivă a unei memorii mai mari decât strictul necesar, deci presupunând ca
există spaţiu de salvare pentru aceşti regiştrii pare rezonabil. Totuşi, rutine, care
necesită salvări mari de date pe stivă, trebuie să apeleze la o stivă locală. De fiecare
dată când se schimbă segmentul de stivă, trebuie ţinut cont de particularităţile
specifice procesoarelor din familia 8086. Astfel, 8086 dezactivează în mod
temporar întreruperile pentru următoarea instrucţiune de fiecare dată când se
asociază nouă valoare unui registru segment. Deci, în secvenţa de cod:

mov ax, offset segmStiva


mov ss, ax
mov sp, offset sfarsitStiva

întreruperile nu pot opri execuţia codului între instrucţiunile mov ss, ax şi mov
sp, offset sfarsitStiva. Dacă acest lucru nu s-ar realiza în mod automat, iar o
întrerupere ar avea loc chiar între cele doua instrucţiuni, codul de tratare apelat va
folosi vechea valoare a deplasamentului împreună cu noua valoare a segmentului
de stiva, o situaţie cu urmări catastrofale. Prin urmare, pentru folosirea acestei
metode de protecţie, orice modificare a unui segment, trebuie urmată de
actualizarea deplasamentului.
Pentru o rutină de tratare a unei întreruperi, declararea unui segment de
stivă locală de 512 baiţi face astfel:

ALIGN
stivaLocala DB 512 dub (0)
sfarsitStiva = $

unde directiva ALIGN este necesară pentru alinierea stivei la un multiplu de cuvânt
procesor. Se declară, apoi, o serie de variabile pentru reţinerea vechilor regiştri:

ssVechi DW 0
spVechi DW 0
axVechi DW 0

În cadrul rutinei se salvează vechile valori ale regiştrilor şi se stabileşte


noua stivă:

mov [cs:ssVechi], ss
mov [cs:spVechi], ss
mov [cs:axVechi], ax
530
mov ax, cs
mov ss, ax
mov sp, offset sfarsitStiva

Urmând ca la terminarea rutinei să se revină la vechile valori:

mov ss, [cs:ssVechi] ;aceasta este ordinea de setare


mov sp, [cs:spVechi] ;a registrilor segment:offset !!!
mov ax, [cs:axVechi]

După cum se observă, o sarcină aparent simplă, cum este cea de folosire a
timer-ului, necesită cunoaşterea profundă a implicaţiilor pe care le implică
utilizarea întreruperilor, nerespectarea regulilor mai sus amintite ducând inevitabil
la terminarea anormală a programului sau, chiar, la o cădere de sistem.

11. 5 Concluzii

Folosirea întreruperilor nu este o sarcină uşoară. De multe ori soluţia cea


mai simplă este folosirea unui limbaj de nivel înalt. Dar, de şi mai multe ori, ceea
ce oferă aceste limbaje, referitor la folosirea directă a hardware-ului, nu este de
ajuns. Prin urmare, folosirea limbajului de asamblare şi a întreruperilor devine
inevitabilă. Sistemele de operare noi (Windows NT, Windows 2000) nu permit
accesarea directă de hardware, oferind soluţii de nivel înalt pentru accesarea
acestuia. Totuşi, scrierea de programe specifice şi dedicate unor sarcini precis
determinate, cum este scrierea unor drivere, accesarea unor echipamente noi,
necesită realizarea unui cod care să ruleze în mod nucleu şi care să acceseze
resursele direct. Astfel, programarea cu întreruperi îşi păstrează actualitatea.
531

12
MACRODEFINIŢII
12.1 Structura macrodefiniţiei

Macrodefiniţia este o secvenţă de program scris în limbaj de asamblare care


corespunde unei prelucrări cu caracter repetat.
Se construiesc macrodefiniţii pentru prelucrări precum:
 împărţirea a două numere cu obţinerea câtului şi restului precum şi a
unei informaţii asupra modului în care s-a efectuat împărţirea (de
exemplu împărţitor zero);
 preluarea unui caracter sau a unui şir de la tastatură;
 afişarea unui caracter sau a unui şir pe ecranul monitorului;
 diverse operaţii de formatare a ecranului, ca de exemplu ştergerea
ecranului, poziţionarea cursorului;
 operaţii cu fişiere: deschiderea, crearea, citirea, ştergerea, închiderea
fişierelor, poziţionarea cursorului;
 aflarea minimului sau maximului dintre elementele unui vector sau a
unei matrice.
Pentru a construi o macrodefiniţie se impune:
 definirea datelor de intrare;
 specificarea schemei logice de prelucrare a datelor de intrare;
 definirea rezultatelor.
Se constituie lista de parametri finali formată din:
 parametrii care corespund datelor de intrare;
 parametrii în care se vor găsi rezultatele;
 parametrii în care se regăsesc informaţii privind modul în care s-au
efectuat prelucrările.
Macrodefiniţia pentru adunarea a două matrice conţine lista de parametrii
formali formată din:
 numele primei matrice (de fapt adresa zonei unde este memorată
matricea);
 numele celei de-a două matrice;
 numărul de linii al matricelor;
 numărul de coloane;
 numele matricei de rezultat;
532

 variabila booleană care are valoarea zero Dacă prelucrarea s-a efectuat
corect, respectiv unu în caz contrar.
Parametrii p1, p2, .. pn sunt descrişi în prima linie sursă a macrodefiniţiei,
în care se mai specifica numele macrodefiniţiei, cuvântul cheie MACRO, linie cu
structura:

nume_macrodefinitie macro [ lista de parametrii ]

Macrodefiniţia conţine definiri de etichete cu caracter local, precum şi


variabile de lucru. Se mai definesc:
 instrucţiunile de salvare a regiştrilor;
 instrucţiunile de prelucrare specifice operaţiei;
 instrucţiunile de restaurare a regiştrilor;
Delimitatorul de sfârşit al macrodefiniţiilor este cuvântul cheie ENDM.
Numele macrodefiniţiilor trebuie să fie ales în aşa fel încât să permită
identificarea cu uşurinţă a acestora. Pentru împărţirea a două numere, antetul
macrodefiniţie arată în felul următor:

divide macro deimpartit, impartit, cat, rest, stare

Pentru adunarea a două numere a, b cu obţinerea rezultatului în s, prima


linie a macrodefiniţiei va fi:sal

aduna macro a, b, s, stare

Pentru salvarea tuturor regiştrilor, se poate construi o macrodefiniţie având


prima linie

salvreg macro

În acest caz absenţa parametrilor se impune pentru că regiştrii sunt


cunoscuţi.
Când se construieşte o macrodefiniţie se are în vedere obţinerea unui grad
de generalitate foarte mare. De asemenea, este necesar ca parametrilor de intrare să
nu li se modifice valorile, regiştrii să fie salvaţi, respectiv restauraţi pentru a nu
influenţa celelalte operaţii din afara secvenţei care defineşte macrodefiniţia.
De exemplu, pentru efectuarea adunării numerelor a, b se construieşte
macrodefiniţia:

aduna macro a, b, s
push ax
mov ax, a
add ax, b
533
mov s, ax
pop ax
endm

12.2 Macroapelul şi macroexpandarea

Dacă procedurile se apelează prin instrucţiunea call, referirea


macrodefiniţiilor se efectuează prin nume.
Macroapelul macrodefiniţiei având prima linie de definire:

nume_macro macro pf1, pf2, .. pfn

se face prin linia sursa:

nume_macro pr1, pr2, .. prn

unde:

nume_macro – numele macrodefiniţiei


pfi – parametrul formal i
pri – parametrul real i
Dacă se consideră macrodefiniţia aduna, definită anterior în subcapitolul
12.1 şi expresia:

e = a + b + c + d

se fac macroapelurile:

aduna a, b, e
aduna e, c, e
aduna e, d, e

În cazul apelului de proceduri prin instrucţiunea call se efectuează salturi


necondiţionate spre secvenţe de program. în cazul macroapelului intr-o etapă a
asamblării programului are loc generarea de secvenţe de instrucţiuni
corespunzătoare macrodefiniţiei. Se înlocuiesc parametri formali cu parametri reali.
Programul astfel asamblat are o lungime mai mare decât programul iniţial.
Se considera macrodefiniţia:

aduna3 macro term1, term2, term3, rez


push ax
mov ax, term1
add ax, term2
add ax, term3
mov rez, ax
pop ax
534
endm

Programul principal arată în felul următor:

…..
mov a, 1
mov b, 3
mov c, 7
mov x, 13
mov y, 2
mov z, 32
aduna3 a, b, c, rez1
aduna3 x, y, z, rez2
aduna3 0, rez1, rez2, e
…..

Într-o etapă de asamblare are loc macroexpandarea. Această operaţie constă


în înlocuirea liniei sursă de macroapel cu instrucţiunile actualizate ale
macrodefiniţiei.
Actualizarea constă în înlocuirea parametrilor formali cu parametri reali.
Programul principal după macroexpandare este:

…...
mov a, 1
mov b, 3
mov c, 7
mov x, 13
mov y, 2
mov z, 32
push ax
mov ax, a
add ax, b
add ax, c
mov rez1, ax
pop ax
push ax
mov ax, x
add ax, y
add ax, z
mov rez2, ax
pop ax
push ax
mov ax, 0
add ax, rez1
add ax, rez2
mov e, ax
pop ax
…..
535

Textul sursă obţinut este mai lung având un număr de instrucţiuni mai mare
decât textul iniţial. Dacă programul asamblor este înzestrat cu componente de
optimizare, atunci se obţine eliminarea din programul macroexpandat a secvenţelor
de instrucţiuni

pop ax
push ax

prima anulând efectul celeilalte. Dezavantajul principal constă în creşterea lungimii


programului prin includerea de secvenţe repetate care diferă numai prin numele
unor variabile. Este rezultatul înlocuirii parametrilor formali, cu parametri reali.

12.3 Etichete locale

Macrodefiniţiile conţin instrucţiuni de salt condiţionat, de salt necondiţionat


si instrucţiuni pentru implementarea structurilor repetitive. Aceste instrucţiuni
presupun definirea si referirea de etichete.
Pentru a elimina ambiguităţile după efectuarea macroexpandării se
consideră soluţia care presupune parcurgerea următorilor paşi:
 etichete utilizate în corpul macrodefiniţiei se definesc cu atributul LOCAL;
 la prima macroexpandare numelui etichetei i se asociază un număr format
din patru cifre compus intre 0000 si 9999; se obţine numele extins al
etichetei;
 referirile etichetelor în instrucţiunile de salt si de ciclare se efectuează cu
numele extins al etichetei.
Macrodefiniţia de alegere a elementului minim dintre trei numere are textul
sursă:

minim macro a, b, c, min


local et1, et2
push ax
mov ax, a
cmp ax, b
jlz et1
mov ax, b
et1:
cmp ax, c
jlz et2
mov ax, c
et2:
mov min, ax
pop ax
endm

Programul principal:
536

...
minim x, y, z, min1
minim w, v, u, min2
minim i, j, k, min3
minim min1, min2, min3, min
...

determină minimul dintr-un şir format din nouă elemente. După macroexpandare,
secvenţa de program va arăta astfel:

...
push ax
mov ax, x
cmp ax, y
jlz et10000
mov ax, y
et10000:
cmp ax, z
jlz et20000
mov ax, z
et20000:
mov min1, ax
pop ax
push ax
mov ax, w
cmp ax, v
jlz et10001
mov ax, v
et10001:
cmp ax, u
jlz et20001
mov ax, u
et20001:
mov min2, ax
pop ax
push ax
mov ax, i
cmp ax, j
jlz et10002
mov ax, j
et10002:
cmp ax, k
jlz et20002
mov ax, k
et20002:
mov min3, ax
pop ax
push ax
mov ax, min1
cmp ax, min2
537
jlz et10003
mov ax, min2
et10003:
cmp ax, min3
jlz et20003
mov ax, min3
et20003:
mov min, ax
pop ax
...

Cele patru cifre ataşate etichetei locale limitează macroapelurile la cel mult
10000 într-un program.

12.4 Variabile locale

Variabilele locale sunt variabile care sunt cunoscute doar în blocul în care
au fost definite. Se pune problema zonelor de memorie, în care să fie alocate aceste
variabile locale:
 segmentul de date global
 segmentul de date propriu
 stiva
În primul caz se profită de faptul că directivele simplificate de definire a
datelor (.date) şi a codului (.code) pot alterna în cadrul programului. Variabilele
locale sunt mai întâi definite cu directiva LOCAL, pentru a nu fi duplicate în apeluri
succesive, apoi declarate în cadrul segmentului de date .data.
Astfel se defineşte o constantă locală, care poate fi aplicată diferitelor date
transmise ca parametri. Fie aceasta constantă

masca equ 00001111b.

Dacă se pune în relaţie cu o variabilă (definită pe un octet), prin operatorul


and, biţii cei mai semnificativi ai rezultatului vor avea valoarea 0, iar biţii mai
puţin semnificativi vor fi asemănători primilor patru biţi ai variabilei .

mask macro x
local masca
.data
masca equ 00001111b
.code
push ax
mov al, x
and al, masca
mov x, al
pop ax
endm
538

În cazul al doilea se creează segmente de date proprii cu ajutorul directivei


SEGMENT, cea ce previne gruparea acestui segment cu alte segmente.
În cazul memorării variabilelor în stivă, trebuie să se parcurgă următorii
paşi:
 se salvează registrul BP în stivă;
 se decrementează registrul SP cu numărul necesar de octeţi pentru
variabilele locale;
 se copiază SP în BP;
 se salvează ceilalţi regiştri folosiţi în program;
 se accesează variabilele locale conform şablonului stivei.
La terminarea macroapelului trebuie avut vedere:
 restaurarea regiştrilor;
 incrementarea registrului SP cu acelaşi număr de octeţi cu care a fost
decrementat la început;
 refacerea registrului BP.
Fie variabilele a,b,c,d,e,f definite pe un octet. Se calculează expresia:

( a*b ) – ( c*d ) + ( e*f )

Se salvează în stiva registrul BP, rezervăm loc în stiva variabilelor var_1 si


var_2, decrementând SP cu patru. Şablonul variabilelor locale arată în felul
următor:

şablon struc
var_1 dw ?
var_2 dw ?
şablon ends

Imaginea stivei, figura 12.1.

var_1

var_2

BP

Figura 12.1 – Conţinutul stivei

Se calculează mai întâi e*f, iar rezultatul este depus în var_1 (accesul se
face prin [bp].var_1 ). Rezultatul c*d este depus în var_2 ( acces prin [bp].var_2 ).
539

După ce se face înmulţirea dintre a si b, se scade din ax var_2, după care se adună
la ax var_1.

calc macro a, b, c, d, e, f, rez


push bp
sub sp,4
mov bp,sp
push ax
xor ax,ax
mov al, e
mul f ;;s-a presupus înmulţire fără semn
mov [bp].var_1, ax
xor ax, ax
mov al, c
mul d
mov [bp].var_2, ax
xor ax, ax
mov al, a
mul b
sub ax, [bp].var_2
add ax, [bp].var_1
mov rez, ax
pop ax
add sp, 4
pop bp
endm

12.5 Macrodefiniţii derivate

Se consideră macrodefiniţii derivate m1, m2, .. mk care formează mulţimea


M a macrodefiniţiilor de bază, ce realizează funcţii elementare. Macrodefiniţii
derivate conţin macroapeluri ale elementelor din mulţimea M.
Macrodefiniţia pentru adunarea a patru numere se construieşte astfel:

aduna4 macro a1, a2, a3, a4, s


push ax
mov ax, a1
add ax, a2
add ax, a3
add ax, a4
mov s, ax
pop ax
endm

Dacă în mulţimea M a macrodefiniţiilor de bază este definită macrodefiniţia


pentru adunarea a două elemente:

aduna2 macro a1, a2, sum


push ax
540
mov ax, a1
add ax, a2
mov sum, ax
pop ax
endm

Pentru adunarea a patru numere se construieşte macrodefiniţia derivată:

aduna4 macro a1, a2, a3, a4, s


aduna2 a1, a2, s
aduna2 s, a3, s
aduna2 s, a4, s
endm

sau se apelează la variabile locale:

aduna4 macro a1, a2, a3, a4, s


local t1,t2
.data
t1 dw ?
t2 dw ?
.code
aduna2 a1, a2, t1
aduna2 a3, a4, t2
aduna2 t1, t2, s
endm

12.6 Macrodefiniţii recursive

Macrodefiniţiile se pot autoapela. Un aspect important este oprirea


recursivităţii. O modalitate pentru aceasta este controlul parametrilor actuali prin
directivele IFNB ( If Not Blank ) si IFB ( If Blank ) care testează existenta sau non-
existenţa unui parametru actual.

pushreg macro r1, r2, r3, r4, r5, r6


ifnb <r1>
push r1
pushreg r2, r3, r4, r5, r6
endif
endm

Dacă primul parametru nu este vid, macrodefiniţia se autoapelează, cu


restul de parametri.
541

12.7 Redefinirea macrodefiniţiilor

Asamblorul MASM permite redefinirea macroapelurilor. Se consideră


macrodefiniţia aduna4. Dacă se foloseşte redefinirea, se ajunge la macroapelul:

aduna4 macro a1, a2, a3, a4, s


local t1,t2
.data
t1 dw ?
t2 dw ?
.code
aduna2 a1, a2, t1
aduna2 a3, a4, t2
aduna2 t1, t2, s
aduna4 macro a1, a2, a3, a4, s
aduna2 a1, a2, t1
aduna2 a3, a4, t2
aduna2 t1, t2, s
endm
endm

Se observă că o macrodefiniţie s-a redefinit în corpul celeilalte. La primul


apel, macrodefiniţia va aloca spaţiu pentru celelalte date, apoi se redefineşte, astfel
încât nu va încerca să realoce datele la apelurile ulterioare. Cu instrucţiunea
PURGE se şterge macroapelul înaintea redefinirii ei.

12.8 Macrodefiniţii uzuale

Pentru înmulţirea unui număr cu 10 este folosită macrodefiniţia imul10.


Macrodefiniţia este apelată cu doi parametrii, unul sursă şi unul destinaţie, operaţia
efectuată fiind:

destinaţie  sursă * 10.

Tipul parametrilor este întreg pe 16 biţi.


Se vor folosi în corpul macrodefiniţiei deplasările pe biţi, care sunt mult
mai rapide decât înmulţirea propriu-zisă.
Regiştrii folosiţi vor fi salvaţi pe stivă şi restauraţi după execuţia rutinei.

; implementarea macrodefiniţiei pentru înmulţirea


imul10 macro dst,src
push ax ; salvarea regiştrilor folosiţi
push bx
mov ax,src ; ax va conţine sursa operaţiei
sal ax,1 ; axax*2
mov bx,ax ; axax
sal ax,1 ; axax*2
542
sal ax,1 ; axax*2
; avem ax=src*8 şi bx=src*2
add ax,bx ; ax src*10
mov dest,ax ; dstax
pop bx ; refacerea regiştrilor folosiţi
pop ax
endm

După cum se observă, macrodefiniţia poate fi folosită cu succes pentru


operanzi care se găsesc în memorie sau regiştri care sunt diferiţi în general de AX
şi BX. Dacă de exemplu destinaţia este BX, acesta nu va fi modificat de rutină,
pentru că este salvat la început şi restaurat apoi. Pentru a fi mai clar, AX şi BX pot
fi surse dar nu pot fi destinaţii ale operaţiei de mai sus.
Pentru extragerea bitului de pe poziţia k se foloseşte macrodefiniţia xbit.
Această macrodefiniţie va extrage bitul de pe poziţia k a sursei în indicatorul de
zero (ZF) lăsând de asemenea în ax sursa din care s-au mascat toţi biţii mai puţin
cel căutat.
Operaţia se face prin generarea unei măşti care se aplică apoi prin ŞI bit cu
bit. Masca este generată prin deplasări succesive la stânga a unui cuvânt care iniţia
avea valoarea 1.
Poziţia corespunde numărului bitului, adică 0 pentru bitul 0,1 pentru bitul 1
etc.

; implementarea macrodefiniţie de extragere a bitului


xbit macro src,k
push bx ; salvarea regiştrilor
push cx
mov bx,src ; bxsursa
mov cl,k ; cxnumărul bitului
mov ax,1 ; se crează masca
shl ax,cl ; ax=1 shl k
and ax,bx ; se aplică masca asupra cuvântului
pop bx ; se refac regiştrii
pop cx ; rezultatul este în ax şi cx
endm

Menţiunile referitoare la parametrii de apel de la precedenta macrodefiniţie


se aplică şi aici, în sensul că avem restricţia că BX nu poate conţine valoarea k.
Valoarea k şi sursa pot fi date şi ca valori imediate.
Pentru ridicarea la puterea a treia se foloseşte macrodefiniţia pow3.
Ridicarea la puterea a 3-a se va face prin două înmulţiri.
Operanzi întregi pe 16 biţi cu semn.

; implementarea macrodefiniţiei de ridicare la puterea a 3-a


pow3 macro dst,src
push ax ; salvarea regiştrilor
543
push bx
mov ax,src ; axsrc
mov bx,ax ; bxax (bcsrc)
imul ax,bx ; axax*bx (ax=src^2)
imul ax,bx ; axax*bx (ax=src^3)
mov dst,ax ; dstax
pop bx ; refacerea regiştrilor
pop ax
endm

Sursa poate fi valoare imediată, registru sau zonă de memorie, iar destinaţia
poate fi registru sau memorie, dar nu poate fi AX sau BX (care vor avea valorile
dinainte de intrarea în blocul respectiv).
Împărţirii a două numere îi corespunde macrdefiniţia xdiv. Această
macrodefiniţie ca avea patru parametrii:
 x - deîmpărţit (sursă)
 y - împărţitor (sursă)
 c - cât (destinaţie)
 r - rest (destinaţie)
 k - test (destinaţie)
Parametrul test va conţine după apelul macrodefiniţiei 0 dacă împărţirea se
putea efectua (împărţitorul diferit de 0) şi 1 dacă împărţitorul este 0 (împărţirea ar fi
invalidă).

; Implementarea macrodefiniţiei x/y c,r


xdiv macro x,y,c,r,k
local xdiv_1,xdiv_2
push cx ; salvează cx
mov cx,y ; cx y
test cx,cx ; test dacă bx=0
jnz xdiv_1 ; dacă nu atunci se continuă
inc cx ; dacă da atunci k 1 (eroare)
mov k,cx
jmp xdiv_2 ; salt la sfârşit
xdiv_1:
push dx ; salvează dx
push ax ; salvează ax
mov ax,x ; ax x
cwd ;axdx,ax(extensia de semn de la 16 la 32
biţi)
idiv cx ; împărţirea (axcât, dxrest)
mov c,ax ; c ax (câtul)
mov r,dx ; r dx (restul)
xor dx,dx ; k 0 (totul e OK)
mov k,dx
pop ax ; refacerea regiştrilor
pop dx
544
xdiv_2:
pop cx ; refacerea lui cx
endm

Se va analiza în cele ce urmează gradul de generalitate a definiţiei,


parcurgând codul acesteia.
Operanzii sursă pot fi imediaţi sau prin adresare directă şi indirectă, iar cei
destinaţie pot fi adresaţi direct sau indirect fără limitări. În cazul în care se transmit
ca parametrii regiştri apar unele restricţii.
Instrucţiunea:

mov cx,y

face ca registrul CX să nu poată fi parametrul x.


Instrucţiunea:

mov c,ax

face ca registrul DX să nu poată fi parametrul c (el este accesat mai târziu).


Instrucţiunile:

pop reg

de la sfârşit determină ca regiştrii AX, CX, DX să nu poată fi folosiţi pentru


operanzii destinaţie (cât, rest, eroare) ai macrodefiniţiei, ei recăpătând valorile
dinaintea apelului.
Deci restricţiile sunt:
 CX nu poate fi deîmpărţitul (parametrul x);
 AX, CX, DX nu pot fi câtul, restul sau parametrul de test (c, r, k).
S-au definit cele două etichete ca local pentru că avem de-a face cu o
macrodefiniţie şi orice expandare a acesteia în program va genera noi definiri ale
acestor etichete. Directiva local specifică asamblorului că referirile la acestea se
vor face cât ţine corpul macroinstrucţiunii.
Macrodefiniţia pentru iniţializarea cu 0 a regiştrilor de lucru este:

Setzeroall Macro
xor ax, ax
xor bx, bx
xor cx, cx
xor dx, dx
xor si, si
xor di, di
endm
545

Macrodefiniţia care evaluează expresia din limbajul C


a1=a2=a3=a4=a5=0

sau expresia din limbajul COBOL


move 0 to a1 a2 a3 a4 a5

este :
Setzero macro a1, a2, a3, a4, a5
mov a1, 0
mov a2, 0
mov a3, 0
mov a4, 0
mov a5, 0
endm

Macrodefiniţia care iniţializează câmpurile a1, a2, a3, a4, a5 definite pe


un bait cu o valoare dată, val, este:
Setval macro a1, a2, a3, a4, a5, val
push ax
mov ax, word ptr val
mov a1, ax
mov a2, ax
mov a3, ax
mov a4, ax
mov a5, ax
pop ax
endm

Macrodefiniţia care interschimbă conţinutul a două zone de memorie a şi b


definite pe un octet este:

Swap1 macro a, b
push ax
mov al, a
mov ah, b
mov b, al
mov a, ah
pop ax
endm

Macrodefiniţia care interschimbă conţinutul a două zone de memorie


definite pe câte doi octeţi (un cuvânt) este:
546
Swap2 macro a, b
push ax
push bx
mov ax, a
mov bx, b
mov b, ax
mov a, bx
pop bx
pop ax
endm

Macrodefiniţia care iniţializează o zonă de memorie definită pe un cuvânt


cu conţinutul altei zone de memorie definite tot pe un cuvânt este:
Setmem macro dest, sursa
push ax
mov ax, ds:[sursa]
mov ds:[dest], ax
pop ax
endm

Macrodefiniţia Strcmp compară două şiruri s1, de lungime l1, si s2, de


lungime l2. Registrul AX conţine deplasamentul primului şir, registrul DX conţine
deplasamentul celui de-al doilea şir, iar BX conţine min{ l1, l2 }. Rezultatul se
transmite prin registrul AX:
-1  şir1 < şir 2
AX = 0  şir 1 = şir 2
1  şir 1 > şir 2

Strcmp macro
push cx
push di
push si
mov si, ax
mov di, dx
mov cx, bx
et1:
mov al, byte ptr ds:[si]
mov dl, byte ptr ds:[di]
cmp al, dl
jne et2
inc si
inc di
loop et1
mov ax, 0
jmp et4
et2:
jb et3
mov ax, 1
547
jmp et4
et3:
mov ax, -1
et4:
pop si
pop di
pop cx
endm

Macrodefiniţia care converteşte o literă mică într-o literă mare este:

Lowtoupper macro char


local final
mov al, ‘a’
jb final
cmp al, ‘z’
ja final
sub al, 20h
final:
nop
endm

În acelaşi fel se definesc şi alte macrodefiniţii uzuale, care vor constitui


într-o bibliotecă.

12.9 Concluzii

Se pune problema alegerii dintre macroapeluri sau proceduri. În cazul


macrodefiniţiilor, texul sursă scris de program devine mai clar şi mai scurt. Un alt
aspect important este câştigul de viteză pe care îl dobândim prin folosirea
macrodefiniţiilor deoarece nu se mai pierde timpul cu memorarea adresei de
revenire în stivă, precum şi cu descărcarea stivei la terminarea procedurii. Marele
dezavantaj este creşterea dimensiunii programului, care după câteva expandări,
poate atinge dimensiuni considerabile.

13
548

PROCEDURI

13.1 Reutilizabilitatea

Secvenţele repetitive şi secvenţele repetabile sunt două lucruri diferite. Două secvenţe
repetitive sunt legate de implementarea structurii repetitive în una din forme (for, do-while, do-
until) rezolvarea secvenţelor repetabile şi-a găsit diferite soluţii dintre care se enumeră:
 plasarea secvenţei în afara modulului principal si referirea ei în diferite puncte ale
acestuia; limbajul COBOL are soluţia elegantă prin utilizarea instrucţiunii
PERFORM; reutilizabilitatea e legată numai de programul respectiv;
 definirea de proceduri, ca unităţi program distincte, cu grad de reutilizabilitate extins şi
la alte programe; procedura are o listă de parametri împărţită în trei subliste: sublista
parametrilor ce conţin datele iniţiale care vor fi prelucrate în procedură, sublista ce
conţine rezultatele prelucrării şi sublista de parametri care modul cum a decurs
prelucrarea (starea procedurii); procedura nu returnează un rezultat, sau returnează un
rezultat de tip vid;
 definirea de funcţii în care lista de parametri conţine numai date de intrare şi rezultatul
prelucrării este unul singur şi va fi returnat, putând fi utilizat direct ca termen în
evaluarea de expresii; funcţiile se construiesc în vederea unei reutilizări foarte largi.
Procedurile şi funcţiile se constituie în biblioteci şi de aceea programatorul
trebuie să dea dovadă de grijă când le construieşte pentru a le asigura
corectitudinea şi un grad de generalizare ridicat. Fără aceste caracteristici nici o
bibliotecă de subprograme nu este operaţională.
Cu atât mai mult soluţiile în limbaje de asamblare care sunt mult mai diversificate, trebuie
realizate astfel încât gradul de reutilizabilitate să atingă nivele care să justifice efortul de a construi
biblioteci de proceduri.
Pentru realizarea conversiilor atât de frecvent utilizate în programe scrise în limbaj de
asamblare se construiesc proceduri. De asemenea, se construiesc proceduri pentru calcule
matriciale, pentru realizarea unor validări, pentru operaţii de intrare/ieşire pe disc, pentru
combinarea unei secvenţe de apeluri de întreruperi. Tot ce se reutilizează în viitor, e preferabil să fie
proiectat ca o procedură.

13.2 Structura unei proceduri

O procedură se descrie prin:


 Parametrii de intrare p1, p2, .., pn ;
 Bloc de prelucrare S
 Parametrii de ieşire r1 ,r2, ..,rm
 Parametrii de stare s1, s2, .., sk,.
Definirea procedurilor este prezentată în figura 13.1
549

p1 r1 = f1 (p1, p2, …, pn) r1


r2 = f2 (p1, p2, …, pn, r1) r2
p2 … ...
rm = fm(p1, p2, …,pn, r1, r2, …,rm-1)
rm
… s1 = 1
s2 = 2 s1
… ...
pn sk = k s2
...
sk

Figura 13.1 – Definirea unei proceduri

La definirea unei proceduri se are în vedere ca n să fie rezonabil, k să fie în


general 1, dacă m=1 se va defini procedură, gestionând cu atenţie rezultatele
pentru a obţine ceea ce caută, existând multe riscuri în a se obţine altceva.
În programul scris în limbaj de asamblare, procedura are structura din figura 13.2.
550

NumeProcedura PROC [{FAR,NEAR}]

Salvare registre

Bloc prelucrare cu referirea


parametrilor formali

Stocarea rezultatelor

Restaurare registre

ret [n]

NumeProcedura ENDP

Figura 13.2 – Structura unei proceduri


De exemplu, procedura PAR pentru criptarea unui text cu metoda lui Cezar după o parolă
este:

date segment
parola db 'parola'
n db n-parola ;lungime parolă
text db 'text secret '
db 12 dup(0)
m db m-text ;lungime text
textcod db 24 dup(),'$'
date ends
stiva segment
db 512h dup(0)
stiva nds
cod segment
ssume cs:cod,ds:date,ss:stiva
start:
mov ax, date
mov ds, ax
call par
mov ax,4c00h
int 21h
551
par proc near
mov cl, m
xor ch, ch
lea di, text
lea si, parola
par1:
mov al, byte ptr[di] ;încarcă
caracterul din text în ax
add al, byte ptr[si] ;adună
caracterul din parolă
add al, cl ;adună poziţia
caracterului
add al,100 ;adună o constantă
mov [di+m], al descarcă caracterul
codificat
inc ah
cmp ah, n
jne par2
xor ax, ax
lea si, parola
dec si
par2:
inc di
inc si
loop par1
ret
par endp
cod ends
end start

13.3 Apelarea procedurilor

Apelarea unei proceduri presupune :


 existenţa unui program apelator
 existenţa procedurii apelate
Dacă programul apelator nu este apelat la nivelul său, va fi numit program
principal, sau funcţie rădăcină intr-o structură arborescentă sau nod iniţial într-o
structură de tip reţea asemănată unui program realizat modular.
Înainte de efectuarea apelării procedurii, în programul principal / procedura
apelatoare trebuie să se găsească o secvenţa care stochează parametri care vor fi
prelucraţi în procedură. Revenirea din procedură se efectuează de regulă la
instrucţiunea care urmează instrucţiunii call. Adresa instrucţiunii ce urmează
instrucţiunii call se gestionează prin intermediul registrului IP.
În procedura apelată există secvenţa pentru salvarea regiştrilor. Este o
regulă de bază, aceea de a nu distruge conţinutul resurselor la care nu are acces în
procedură.
Dacă aceeaşi procedură este apelată în mai multe puncte din programul
principal schema fluxului de execuţie este dată în figura 13.2.
552

call sume

sume PROC
call sume

call sume ret


sume ENDP

Figura 13.2 – Apelarea multiplă a unei proceduri


Dacă programul principal apelează procedura Suma, iar procedura aceasta
apelează procedura Compara, schema de execuţie este dată în figura 13.3.

Suma PROC Compara PROC


Figura 13.3 – Apelarea în cascadă a procedurilor

Call suma Call compara

RET RET
553

Program principal

Definire parametri reali


Procedură (PROC)

Salvare registre
Pregătirea transmiterii
parametrilor reali
Prelucrare referind
APEL PROCEDURĂ (call) parametri formali

Prelucrarea rezultatelor Stocarea rezultatelor


prelucrării

Restaurare registre

Revenire (RET)

Figura 13.4 – Schema standard de structurare/apel/revenire


Se observă apariţia a două concepte: parametri reali şi parametri formali. Pentru
programatorul ce utilizează limbajul de asamblare se evidenţiază semnificaţia acestora.
În programul scris în limbajul de asamblare se lucrează cu registre şi cu
zone de memorie. Deci, dacă parametri sunt operanzi, în mod obligatoriu sunt ori
registre, ori zone de memorie. Se lucreză cu: identificatori (registru sau zonă de
memorie), adresă, lungime şi conţinut.

Cazul 1:
 parametri p1,p2, ...pn sunt în număr restrâns şi sunt stocaţi în registre;
 rezultatele r1,r2, ...rm sunt în număr restrâns şi sunt stocaţi în registre;
 informaţia de stare se stochează într-un registru.
În această situaţie, pregătirea transmiterii parametrilor înseamnă iniţializare de registre,
apelul procedurii înseamnă salt către prima instrucţiune executabilă din procedură. În procedură se
salvează registre pe stivă. Referirea parametrilor formali, va însemna utilizarea registrelor. Stocarea
rezultatelor se face în registre. Stocarea stării se va face într-un registru (CX = 0 dacă prelucrarea s-
a efectuat corect, CX = -1 dacă prelucrarea nu s-a efectuat corect, de exemplu). Se returnează
registrele. Se are în vedere ca prin restaurare să nu se distrugă din rezultatele obţinute sau registru
de stare al procedurii.
Parametrii reali sunt registrele care se pregătesc în programul apelator.
Parametrii formali sunt aceleaşi registre pe care le utilizează procedura. Li se spune aşa,
probabil pentru faptul că programatorul când scrie procedura nu are în aceşti parametri valori
efective cu care să lucreze. El numai îşi imaginează. Doar după ce a fost apelată, în registru se
554
găsesc informaţii care fac obiectul prelucrării. Lucrul cu registre este cel mai simplu mod de
transmitere a parametrilor, listele de parametri formali, respectiv, reali, suprapunându-se din toate
punctele de vedere.

Cazul 2:
 Conţinutul zonelor de memorie care formează parametri reali se transmit pe stivă,
numărul parametrilor fiind fix; parametri formali sunt zonele din stivă care la
procesarea listei au expresii de referire precis construite; aceasta este transmiterea
parametrilor prin valoare; în procedură se lucrează cu copiile parametrilor reali, aflate
în stivă; rezultatele se depun fie în registre (preferabil), fie pe stivă.
Parametrii reali sunt zone de memorie diferite în programul apelator. Parametri formali
sunt zone ale stivei unde se află copii ale valorilor parametrilor reali. Cele două liste se referă la
zone de memorie diferite care au însă acelaşi conţinut. Este cunoscut cazul procedurii de
interschimb a conţinutului dintre două zone de memorie “a” şi „b”.

Parametrii reali Procedură de interschimb

A - 15 Stivă

B - 20 [BP + 4] - 15

[BP + 6] - 20

Registrele AX şi BX se folosesc pentru interschimb:


AX = [BP + 4]
BX = [BP + 6]
[BP + 4] = BX
[BP + 6] = AX

Cazul 3:
 Zonele de memorie ale căror conţinut urmează să fie prelucrată în proceduri sunt de
tipuri şi lungimi foarte diferite; este convenabil pe stivă să se transmită adresele
zonelor de memorie care formează lista parametrilor reali se construieşte în acest fel
listă de adrese a parametrilor reali, în procedură, parametri formali sunt de fapt
variabile pointer care permit referirea din procedură a zonelor de memorie diferite în
programul apelator; acesta este transmitere prin adresă.
Procedura de interschimb va realiza corect operaţia dacă pe stivă se vor găsi adresele
zonelor de memorie “a” şi „b”; procedura va lucra direct pe ele si într-adevăr va efectua
interschimbul.

Cazul 4:
 Se defineşte un segment de date comune tuturor procedurilor si programului apelator.
Printr-o încărcare corectă a adresei acestui segment se va opera cu parametri reali
definiţi în el atât din programul apelator, cât şi din orice procedură segmentul de date
este comun întregii operaţii. Se realizează transmiterea prin referinţă, variabilele cu
anumiţi identificatori se află în segmentul comun aplicaţiei şi de acolo se utilizează
conţinutul zonei de memorie.

13.4 Locul procedurilor


555

Mecanismul de definire a procedurilor permit plasarea lor în diferite


moduri, figura 13.5.

.code .code
start: start:
a1 proc ...
... call b1
ret ...
a1 endp call b2
a2 proc ...
... mov ax, 4c00h
ret int 21h
a2 endp b1 proc
... ...
call a1 …
... ret
call a2 b1 endp
... b2 proc
mov ax, 4c00h ...
int 21h ret
end start b2 endp

a) b)

Figura 13.5 – Poziţii ale procedurilor

Astfel se observă că există:


 Proceduri definite în corpul programului principal, figura 13.5 a;
 Proceduri definite după secvenţa din segmentul de cod, figura 13.5 b:

mov ax, 4c00h


int 21h

13.5 Instrucţiuni specifice lucrului cu proceduri

Instrucţiunile specifice lucrului cu proceduri sunt :


 call – pentru apelul unei proceduri
 ret – pentru revenirea în programul apelator.
Semnificaţia instrucţiunilor diferă funcţie de modul în care sunt poziţionate cele două
elemente:
Dacă procedura se află în acelaşi segment cu programul apelator şi este apelată direct prin
nume, instrucţiunea call pune în stivă valoarea curentă a lui IP iar apoi dă controlul procedurii:

SP = SP – 2
(SP) = IP ;push IP
IP = IP + D16

unde D16 reprezintă deplasarea între destinaţie şi instrucţiunea ce urmează lui CALL în programul
apelator.
556
Instrucţiunea ret efectuează:

IP = (SP) ;salt la instrucţiunea următoare lui CALL


SP = SP + 2 ;revenirea la baza stivei (pop IP).

Se consideră programul:

Parametri segment
a dw 10
b dw 15
c dw 21
d dw 37
s dw ?
Parametri ends

Stiva segment
Dw 100h dup (?)
varf label word
Stiva ends

Apelator segment
ASSUME cs:code, ds:parametri, ss:stiva
start:
mov ax, parametri
mov ds, ax
mov ax, stiva
mov ss, ax
mov sp, offset varf
mov ax, offset a
push ax
mov ax, offset b
push ax
mov ax, offset c
push ax
mov ax, offset s
push ax
call NEAR ptr suma
mov ax, s
add ax, d
mov s, ax
mov ax, 4c00h
int 21h

;procedura în acelaşi segment cu programul apelator


suma proc NEAR
push bp
mov bp, sp
push ax
xor ax, ax
add ax, [bp+10]
add ax, [bp+8]
add ax, [bp+ 6]
557
mov [bp+ 4], ax
pop ax
pop bp
ret 8h
Apelator ends

Dinamica stivei pentru apelarea procedurii suma din program este:

Stivă Semnificaţie Adresare


Offset a Plasare adresa (offset) a [BP+10]
Offset b Plasare adresa b [BP+8]
Offset c Plasare adresa c [BP+6]
Offset s Plasare adresa rezultat s [BP+4]
IP Ape procedura sumă [BP+2]
BP Salvează baza stivei [BP+0]
Ax Salvare pe stivă valoare registru AX [BP-2]
Ax Refacere registru AX -
BP Refacere registru BP -
IP Reface IP (se pregăteşte return) -
SP = SP +8h Scoate parametri din stivă -

Dacă procedura se află în acelaşi segment şi este apelată indirect instrucţiunea call
realizează:

SP = SP – 2
(SP) = IP
(IP) = EA

unde EA reprezintă adresa efectivă (effective address).


Dacă programul apelator se află într-un registru şi procedura apelată se află în alt segment,
instrucţiunea call realizează:

SP = SP – 2
(SP) = CS
SP = SP – 2
(SP) = IP

Instrucţiunea ret pentru acest caz realizează:

IP = (SP)
SP = SP + 2
CS = (SP)
SP = SP + 2

Dinamica stivei când programul apelator se află în segmentul apelului, iar procedura se află
în segmentul procedurii este:

Parametri segment
a dw 10
b dw 15
558
c dw 21
d dw 37
s dw ?
Parametri ends

Stiva segment
Dw 100h dup (?)
varf label word
Stiva ends

Apelator segment
ASSUME cs:code, ds:parametri, ss:stiva
start:
mov ax, parametri
mov ds, ax
mov ax, stiva
mov ss, ax
mov sp, offset varf
mov ax, offset a
push ax
mov ax, offset b
push ax
mov ax, offset c
push ax
mov ax, offset s
push ax
call far ptr suma
mov ax, s
add ax, d
mov s, ax
mov ax, 4c00h
int 21h
Apelator ends

Procedura segment
ASSUME cs:procedura
suma proc FAR
push bp
mov bp, sp
push ax
xor ax, ax
add ax, [bp+12]
add ax, [bp+10]
add ax, [bp+ 8]
mov [bp+ 6], ax
pop ax
pop bp
ret 8h
suma endp
procedura ends

Stivă semnificaţie Adresare


559

Offset a Plasare adresa (offset) a [BP+12]


Offset b Plasare adresa b [BP+10]
Offset c Plasare adresa c [BP+8]
Offset s Plasare adresa rezultat s [BP+6]
CS Salvare registru de cod pe stivă (CALL) [BP+4]
IP Ape procedura sumă [BP+2]
BP Salvează baza stivei [BP+0]
Ax Salvare pe stivă valoare registru ax [BP-2]
Ax Refacere registru ax -
BP Refacere registru BP -
IP Reface IP (se pregăteşte return) -
CS Reface segmentul de cod -
SP = SP +8h Scoate parametri din stivă -

Dacă programul apelator şi procedura se află în segmente diferite, iar apelul este direct,
instrucţiunea CALL realizează:

SP = SP – 2
(SP) = CS
SP = SP – 2
(SP) = IP
(IP) = (EA)
(CS) = (EA) + 2

Instrucţiunea ret poate fi definită împreună cu o expresie constantă:

ret expresie

care evaluează şi devine o valoare de adunare la registru de stivă (SP) ca parte din instrucţiune
determinând executarea secvenţei:

SP = SP + expresie

pentru revenire în programul apelator:

IP = (SP)
SP = SP + 2
CS = (SP)
SP = SP +2
SP = SP + expresie.

Aceasta este echivalentă cu scoaterea din stivă a unui număr de baiţi specificat prin
expresie; se foloseşte la eliminare din stivă a parametrilor care au fost puşi înainte de apelul
procedurii.
Făcând modificări asupra conţinutului stivei în procedura apelată este posibilă revenirea în
programul apelator la altă instrucţiune şi chiar la instrucţiuni aflate în segment de cod diferit
segmentului programului apelator. Se recomandă folosirea acestei facilităţi doar pentru protejarea
codului.
560

14
PRELUCRĂRI ÎN VIRGULĂ MOBILĂ
14.1 Scurtă istorie a prelucrărilor în virgulă mobilă

Procesoarele din familia 80x87 au fost construite cu scopul precis de a


creşte viteza de lucru a calculatoarelor în operaţiile matematice complexe. Până la
versiunea 80387 coprocesoarele puteau sa existe sau nu în calculatoare. De
exemplu, un procesor 80386 lucrează foarte bine cu coprocesorul 80287 sau cu
80387. Mai târziu, la apariţia procesorului 80486, coprocesorul a fost înglobat în
procesor, pentru îmbunătăţirea performanţelor. Astfel, un procesor 80486 conţine
un procesor 80387. Instrucţiunile procesorului 80486 sunt cele ale procesorului
80386, cele ale coprocesorului 80487 şi câteva în plus.
Toate coprocesoarele lucrează insa cu numere reprezentate pe 80 de biţi.
Procesoarele nu trebuie sa se ocupe de conversia numerelor pe 8, 16, 32 sau 64 de
biţi în cele pe 80 de biţi, deoarece instrucţiunile care mută datele din regiştrii
procesorului în cei ai coprocesorului convertesc automat numerele în formatul de
80 de biţi.
Unele din numerele de 80 de biţi au o semnificaţie speciala, ele pot
reprezenta numărul infinit, un număr cu parte fracţionară în perioadă sau un NaN
(Not a Number). NaN este o valoare care se obţine în momentul în care se încearcă
rezolvarea unor calcule de genul 0/0 sau /.
O altă caracteristica a coprocesoarelor este aceea a executării secvenţiale a
instrucţiunilor. De exemplu, dacă avem două instrucţiuni, cea de-a doua nu este
executată până când prima instrucţiune nu a fost terminată.

14.2 Resurse

Pentru efectuarea prelucrărilor în virgulă mobilă este utilizat un coprocesor


sau se construiesc proceduri care efectuează conversii şi calcule cu utilizarea
aritmeticii binare cu biţii reprezentând caracteristica şi mantisa.
Astfel dacă se aduna numerele
A = 0,37·102
B = 0,123·104
se parcurg următorii paşi.
 se omogenizează numerele, în aşa fel încât sa aibă acelaşi exponent:
A’ = 0,0037·104
B = 0,123·104
561

 se efectuează operaţia de adunare:


C = A’ + B = 0,1267·104
 se procedează la normalizare, prin eliminarea zerourilor de după virgulă
şi la diminuarea exponentului; ca în cazul scăderii:
C=A–B
unde:
A = 0,736·104
B = 0,731·104
C = A – B = 0,005·104 = 0,5·102
Toate aceste operaţii se obţin prelucrând biţii din reprezentările constantelor
în virgulă mobilă pe 4, 8, 10 baiţi, utilizând instrucţiunile aritmeticii
binare. Procedurile emulează un coprocesor numai dacă asigură
efectuarea gamei complete de operaţii de prelucrare în virgula mobila.
De exemplu, o procedura de normalizare se efectuează astfel:

000001101……

 se deplasează spre stânga K=5 poziţii.


 se scade din exponent cat înseamnă K poziţii, cu consecinţe asupra
semnului dacă este cazul.
Pentru o procedura de adunare vor exista următoarele operaţii:

E1 M1

E2 M2

 dacă E1 = E2 atunci M3 = M1 + M2
 dacă E1  1 vedem cu cat şi mărim E3. Z1 reprezintă partea întreagă a
mantisei, în acest caz a lui M3.
Dacă este utilizat un coprocesor, resursele disponibile ale acestuia sunt:
 opt registre ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7; fiecare registru
are o lungime de 10 baiţi;
 o structura de baiţi, denumită status word, destinată indicatorilor de
condiţie; astfel, coprocesorul 8087 are o structura de 16 biţi cu
indicatorii de condiţie specificaţi în figura 14.1.
562

Utilizare

Invalid operation
exception
Denormalized
operand exception
Zerodivide exception

Overflow exception

Underflow exception

Precision exception

Interrupt exception

Condition code 0

Condition code 1

Condition code 2

Stack-top pointer

Condition code 3

Busy signal

Figura 14.1 – Indicatorii de condiţie ai coprocesorului

În caz de eroare biţii 0, 1, 2, 3, 4, 5, 8, 9, 10, 14 sunt setaţi pe 1. Biţii 13, 12,


11 arată care dintre cei 8 regiştri este vârf de stiva. Aceşti 3 biţi
necesari gestionarii stivei vor fi numiţi în continuare PVST (pointer
pentru gestionarea vârfului de stivă).

PVST  {0, 1, 2, 3, 4, 5, 6, 7}

Oricare dintre regiştri poate fi vârf de stivă. Dacă PVST este 6, o operaţie
de incrementare conduce ca PVST sa devină 7. Dacă PVST este 7, o
operaţie de incrementare va face ca PVST sa devină 0. În cazul în care
PVST are valoarea 0 şi se efectuează o operaţie de decrementare, PVST
va avea valoarea 7. Regiştrii funcţionează ca stivă circulară.
Coprocesorul accesează operanzi definiţi în memorie cu descriptori
de tip DD, DT sau DQ. Regiştrii coprocesorului sunt construiţi în stivă
(figura 14.2.), efectuarea operaţiilor fiind precedată de operaţii de
563

modificare a vârfului de stivă, operaţii specifice introducerii în stivă


(push) sau extragerii din stiva (pop) a operanzilor.

Figura 14.2 – Regiştrii coprocesorului 8087

Operaţiile de prelucrare a datelor efectuate de coprocesor sunt


împărţite în mai multe clase:

 R–R ceea ce înseamnă că ambii operanzi se află în regiştri (pe


stivă) şi prelucrările se efectuează în unul din următoarele doua moduri:
o un operand este conservat iar celalalt va conţine rezultatul
prelucrării ;
o ambii operanzi sunt conservaţi şi rezultatul prelucrării este
introdus la baza stivei; operanzii conservaţi, iar vârful stivei
glisează.
564

 R–M ceea ce înseamnă că un operand se afla în registru şi altul se


afla în memorie; operandul din registru este copiat în alt registru şi la
baza stivei se va găsi rezultatul, fie operandul este modificat şi el
reprezintă rezultatul prelucrării; poziţia operandului aflat în memorie
determina uneori ca rezultatul prelucrării sa fie stocat în această zona, în
cazul instrucţiunilor de forma: cod_operatie M,R.
Structura terminală a mnemonicelor indica prin prezenţa literei p, efectuarea
de operaţii de introducere în stiva (push), sau de extragere din aceasta
(pop). Utilitatea acestui mod de lucru este evidenţiată atunci când se
evaluează expresii şi regiştrii conţin rezultate parţiale care se refera
direct, fără a mai fi nevoie de operaţii de încărcare (load) sau memorare
(store).

14.3 Setul de instrucţiuni

Instrucţiunile coprocesoarelor din familia 80x87 sunt clasificate în şase


grupe distincte:
 instrucţiuni de transfer al datelor (de încărcare) ;
 instrucţiuni aritmetice ;
 instrucţiuni de comparare ;
 instrucţiuni transcedentale ;
 constante ;
 instrucţiuni de control al procesorului .
Pentru o gestionare corectă a operaţiilor care se desfăşoară în coprocesor a
fost implementată structura de control a operaţiilor, denumită control word.
Comportamentul acestui registru este asemănător cu cel al registrului de stare.
Registrul de control are o lungime de 16 biţi, valorile acestora fiind detaliate în
figura de mai jos:

Utilizare

Invalid operation exception


mask
Denormalized operand
exception mask
Zerodivide exception mask

Overflow exception mask

Underflow exception mask


565

Precision exception mask

Reserved
Interrupt enable mask
0 = Interrupts enabled
1 = Interrupts disabled
Precision Control
00 = 24 bits
01 = reserved
10 = 53 bits
11 = 64 bits
Rounding Control
00 = round to nearest or even
01 = round down
10 = round up
11 = truncate

Infinity Control
0 = Projective
1 = Affine
Reserved

Figura 14.3 – Indicatorii de control

Instrucţiunile de încărcare au rolul de a copia conţinutul unei zone de


memorie sau a unui registru numit sursa (SRS), intr-un registru specificat.
Instrucţiunea FLD SRS memorează în ST conţinutul din SRS convertit de la tipul
real scurt sau real lung la tipul real cu reprezentare pe 10 baiţi.
Instrucţiunea FILD SRS memorează în ST conţinutul din sursa SRS
convertit de la tipul întreg (scurt sau lung) la tipul real pe 10 baiţi.
Instrucţiunea FDLD SRS converteşte conţinutul zecimal împachetat din
SRS la tipul real reprezentat pe 10 baiţi şi încarcă în ST.
Instrucţiunile FLD, FDLD şi FDLD decrementează pointerul stivei formată
din regiştrii ST(I), cu I=1,..,7 permiţând rămânerea pe stivă şi a celorlalţi operanzi.
Instrucţiunea FST DST efectuează copierea din registrul ST în operandul
DST a conţinutului convertit de la tipul real pe 10 baiţi la tipul real scurt sau real
lung.
Instrucţiunea FISI DST copiază din registrul ST în operandul DST,
conţinutul convertit la tipul întreg.
Instrucţiunea FBSTP DST converteşte la tipul zecimal împachetat
conţinutul registrului ST şi îl memorează în zona de memorie referită de DST.
Instrucţiunile FSTP şi FISTP realizează aceleaşi prelucrări precum
instrucţiunile FST şi FIST cu deosebirea ca în plus incrementează pointerul cu
care se gestionează vârful stivei.
566

Instrucţiunea FXCH STi schimbă conţinutul registrului STi cu ST. Dacă


lipseşte operandul interschimbul se efectuează între regiştrii ST şi ST1.

Instrucţiunile aritmetice corespund operaţiilor elementare (adunare,


scădere, înmulţire, împărţire, extragere modul, schimbare semn, extragere radical,
scalare, obţinerea restului împărţirii).
În cazul în care instrucţiunea are ca ultima litera a mnemonicii litera P,
aceasta are semnificaţia operaţiei de eliberare a stivei (pop) ceea ce corespunde
incrementării pointerului de stivă.
De exemplu, instrucţiunile FADD şi FADDP realizează operaţia de
adunare. În plus, instrucţiunea FADDP incrementează pointerul de gestionare a
vârfului de stivă (PVST = PVST + 1).
Instrucţiunea de adunare a numerelor reale FADD DST,SRS corespunde
operaţiei:
(DST) := (DST) + (SRS)

Instrucţiunea de adunare a numerelor reale FADDP DST,SRS corespunde


operaţiilor:

(DST) := (DST) + (SRS)


PVST := PVST + 1

Instrucţiunile de scădere numere reale FSUB DST, SRS şi FSUBP DST,SRS


corespund operaţiilor:

(DST) := (DST) - (SRS), respectiv

(DST) := (DST) - (SRS)


PVST := PVST + 1

Instrucţiunea FISUB DST,SRS efectuează operaţia de scădere a datelor de


tip întreg, scurt sau lung.

(DST) := (DST) + (SRS)

Instrucţiunile de înmulţire FMUL DST,SRS şi FMULP DST,SRS


efectuează operaţiile:
(DST) := (DST) * (SRS)

respectiv:

(DST) := (DST) * (SRS)


567

PVST := PVST + 1

Instrucţiunile FDIV DST,SRS şi FDIVP DST,SRS efectuează împărţirea:

(DST) := (DST) / (SRS)

În plus FDIVP incrementează pointerul pentru gestionarea stivei. Pentru


împărţirea numerelor întregi se utilizează instrucţiunea FIDIV.
Pentru obţinerea valorii absolute se utilizează instrucţiunea FABS, fără
operanzi care realizează operaţia:

(ST) := | ST |

Schimbarea semnului se efectuează de către instrucţiunea FCMS cu


operandul implicit ST, care efectuează operaţia:

(ST) := -(ST)

Pentru extragerea rădăcinii pătrate se utilizează instrucţiunea FSQRT cu


operandul implicit ST, care realizează operaţia descrisa prin:

(ST) := (ST )

Rotunjirea la întreg a operandului implicit ST este efectuată de instrucţiunea


FRNDINT (Round to integer).
Operaţia de scalare corespunde înmulţirii unui număr aflat în registrul ST cu
2n, n aflat în registrul ST(1). Mnemonica instrucţiunii care implementează această
instrucţiune este FSCALE.
În anumite operaţii este necesară preluarea repetată exponentului şi a părţii
fracţionare, ceea ce impune memorarea lor ca operanzi distincţi, figura 14.4.

exponent parte fractionara

ST(1) ST
Figura 14.4 – Separarea ca operanzi distincţi a exponentului şi a părţii
fracţionare

Instrucţiunile de comparare compara doi operanzi şi poziţionează


indicatorii de condiţie.
568

Instrucţiunea FCOM SRS efectuează operaţia (ST) – (SRS) şi poziţionează


indicatorii de condiţie.
Instrucţiunea FCOMP SRS efectuează operaţia (ST) – (SRS) şi
incrementează pointerul pentru gestionarea stivei. Pentru compararea datelor de tip
întreg se folosesc instrucţiunile FICOM şi FICOMP.
Pentru compararea cu zero a elementului din vârful stivei este folosită
instrucţiunea FTST, care efectuează operaţia (ST) – 0.0 .

Funcţiile transcedentale au implementate expresii precum:

f(x) = 2x – 1

g(x) = arctg(x)

r(x) = tg(x)

h(x,y) = y*log2(x), cu x  (0,  ) şi y  R

p(x,y) = y*log2(x + 1), cu x  (0, 1 - 2 / 2 ) şi y  R

Instrucţiunea F2XM1 evaluează funcţia f(x). Dacă x  [0; 0.5] şi x este


memorat în ST, atunci

(ST) := 2(ST) - 1

Instrucţiunea FPATAN evaluează funcţia g(x). Instrucţiunea FPTAN


evaluează funcţia r(x) pentru x  (0; /4).
Instrucţiunea FYL2X evaluează funcţia h(x,y); operandul x se găseşte în
registrul ST, iar operandul y se găseşte în registrul ST(1).
Instrucţiunea FYL2XP1 evaluează funcţia p(x,y). Variabila x se memorează
în registrul ST, iar variabila y se memorează în registrul ST(1).

Instrucţiunile pentru gestionarea şi manipularea constantelor


decrementează pointerul PVST şi iniţializează registrul ST cu o anumită valoare,
specificată prin instrucţiune.
Instrucţiunea FLDZ decrementează PVST şi iniţializează registrul ST cu
valoarea zero real (0.0) .
Instrucţiunea FLD1 decrementează PVST şi iniţializează registrul ST cu
constanta 1.0 .
Instrucţiunea FLDPI decrementează PVST şi iniţializează registrul ST cu constanta
PI.
569

Instrucţiunea FLD2E decrementează PVST şi iniţializează registrul ST cu


constanta log2 e .
Instrucţiunea FLD2T decrementează PVST şi iniţializează registrul ST cu
constanta log2 10 .
Instrucţiunea FLDLG2 decrementează PVST şi iniţializează registrul ST cu
constanta log10 2.
Instrucţiunea FLDLN2 decrementează PVST şi iniţializează registrul ST cu
constanta loge 2.

Instrucţiunile de control vizează iniţializarea procesorului (FINIT),


punerea pe unu a biţilor ce formează măşti pentru întreruperi (FDISI), punerea pe
zero a biţilor ce formează măşti ale întreruperilor (FENI), incrementarea
pointerului stivei (FINCSTP), decrementarea pointerului stivei (FDECSTP),
memorarea registrului de stare (FSTSW), memorarea registrului de control
(FSTCW), încărcarea registrului de control (FLDCW).
Instrucţiunea FNOP copiază registrul ST în el însuşi. Este folosită pentru
întârzierea execuţiei proceselor.
Pentru salvarea regiştrilor se foloseşte instrucţiunea FSAVE, iar pentru
restaurarea regiştrilor se foloseşte instrucţiunea FRSTOR. Zona de memorie în
care se salvează regiştrii are o lungime de 94 de baiţi.
Pentru dealocarea unui registru se foloseşte instrucţiunea FFREE, care
setează pe zero flagul corespunzător registrului respectiv, aceasta semnificând ca el
este gol.
Pentru salvarea pe stivă a regiştrilor de stare se foloseşte instrucţiunea
FSTSW, iar pentru salvarea informaţiilor privind procesele, regiştrii de control şi
de stare, registrul tag şi pointerii operanzilor se foloseşte instrucţiunea FSTENV.
Zona de memorie în care se salvează va avea o lungime de 14 baiţi.

14.4 Sintaxa instrucţiunilor

Instrucţiunile pentru operaţii în virgulă mobilă (floating point instructions)


încep cu litera F. Mnemonicele conţin elemente comune cu instrucţiunile
limbajului de asamblare (add – addition, st – store, ld – load, cmp – compare, div –
divide, sub – subtract, sqrt – square root, abs – absolute value, chs – change sign,
mul – multiply, cos – cosine, sin – sine, patan – partial arctangent, ptan – partial
tangent).
Instrucţiunile sunt de tip R – R, R – M, R – I cu luarea în considerare a
faptului ca registrul acumulator este considerat ST(0), utilizat în formulele implicite
de scriere a instrucţiunilor.
Astfel putem întâlni următoarele tipuri de instrucţiuni:

Fmnem [[op1][,op2]]
570

sau

Fmnem [[destinaţie][,sursa]]

Prin utilizarea parantezelor drepte se realizează opţionalitatea operandului


în expresie. Definirea explicită a operanzilor corespunde construcţiei:

Fmnem destinaţie, sursa

cu semnificaţia:

destinaţie := destinaţie operaţie sursa

De exemplu, în instrucţiunea:

FADD ST(1),ST

avem

ST(1) := ST(1) + ST

Operanzii ST(1) – destinaţie, ST(0) – sursa sunt operanzii impliciţi pentru


această operaţie. Dacă în program se scrie FADD şi lipsesc operanzii, se
subînţelege ca registrul ST(1) este sursa, iar ST este destinaţie.
Dacă se utilizează lista de operanzi în mod explicit, are loc atribuirea de rol
sursa a unui anumit registru, respectiv, de rol destinaţie pentru un alt registru.
Instrucţiunea:

FADD ST(i),ST

corespunde operaţiei

ST(i) := ST(i) + ST

iar instrucţiunea

FADD ST,ST(i)

corespunde operaţiei

ST := ST + ST(i)
571

În cazul în care un operand este definit în memorie se utilizează formele de


instrucţiuni:

Fmnem [[reg][,numevariabila]]

unde operandul sursa se afla numai în memorie, iar operandul destinaţie se afla în
registru.
Instrucţiunea FADD ST(2), preţ corespunde expresiei:

ST(2) := ST(2) + pret

Iar instrucţiunea FADD pret corespunde expresiei:

ST := ST + pret

Efectul execuţiei instrucţiunilor

Instrucţiunile au ca efect atât realizarea de operaţii simple (adunări, scăderi,


memorări, încărcări, iniţializări) cit şi extragerea sau depunerea pe stiva a
rezultatelor cu efectele de translatare specifice acestui tip de structura de date care
este stiva.
Pentru a evidenţia efectele execuţiei unei operaţii se impune:
 prezentarea conţinutului regiştrilor ST, ST(1), ST(2), …, ST(7) înainte de
execuţie;
 indicarea efectului execuţiei propriu-zise;
 prezentarea modului în care se schimba conţinutul regiştrilor prin
efectele operaţiilor de depunere pe stiva (push), respectiv, de extragere
din stiva (pop) care însoţesc execuţia instrucţiunilor de program.

14.5 Forma externă a instrucţiunilor

Instrucţiunile fără operanzi presupun fie utilizarea implicită a registrului


vârf de stivă ST(i), fie utilizarea implicită regiştrilor ST(0) şi ST(1).
Instrucţiunea FADD realizează

[ST(0)] := [ST(0)] + [ST(1)]

adică aduna conţinutul registrului ST(1) la conţinutul registrului ST(0). Rezultatul


se găseşte în registrul ST(0).
Instrucţiunea FXCH interschimbă conţinutul registrului ST(0) cu conţinutul
registrului ST(1).
Instrucţiunea FSCALE realizează operaţia:
572

ST(0) := ST(0)*2ST(1)

Dacă registrul ST(1) conţine valoarea 4, conţinutul registrului ST(0) va fi


multiplicat de 16 ori. Dacă registrul ST(1) conţine –3, conţinutul registrului ST(0)
va fi împărţit la 8.
Instrucţiunea FCOM realizează compararea registrului
ST(0) cu registrul ST(1) şi poziţionează indicatorii de condiţie.
Instrucţiunea FTST compară conţinutul registrului ST(0) cu 0.0 şi setează
indicatorii de condiţie.
Instrucţiunea FLDLG2 realizează încărcarea în ST(0) a valorii log102 =
0.30102…, cu o aproximaţie de 19 cifre zecimale.
Instrucţiunea FLDPI încarcă în ST(0) numărul PI, cu o precizie de 19 cifre.
Instrucţiunea FLDZ încarcă în registrul ST(0) valoarea constantei zero (0.0)
.
Instrucţiunea FLD2T încarcă în registrul ST(0) valoarea log210 cu o
precizie de 19 cifre zecimale.
Instrucţiunea FLD2E încarcă registrul ST(0) cu valoarea log2e cu o precizie
de 19 cifre zecimale.
Instrucţiunile cu un operand presupun utilizarea implicită a registrului
ST(0).
Astfel, dacă registrul ST(1) conţine valoarea 275, instrucţiunea FLD ST(1)
are ca efect iniţializarea registrului ST(0) cu această valoare. După execuţia
instrucţiunii regiştrii vor arata ca în figura 14.5.:

ST(0) ST(1) ST(2)


340 275 ?
FLD ST(1) 275 340 275
? ? ?
Figura 14.5 – Modificările din regiştrii la execuţia instrucţiunii FLD ST(1)

14.6 Exemple

Se va construi un program care să înmulţească două


numere în virgulă mobilă folosind instrucţiunile coprocesorului.
Variabilele x1 şi x2 sunt definite pe 64 biţi. Instrucţiunea de
înmulţire are ca parametri impliciţi pe st(0) şi pe st(1) iar
rezultatul este stocat în st(0).
Pentru vizualizarea rezultatului se va utiliza facilitatea debugger-
ului de a afişa conţinutul registrelor coprocesorului.
573
;Program de demo lucru cu coprocesor
;Realizeaza inmultirea a doua numere x1 si x2
; daca exista coprocesor. Altfel afiseaza un mesaj de eroare.
.model small
.286
.data
NuInstalat db 'CoProcesor neinstalat',10,13,'$'
ctrl_87 dw lbffh; precizia =64
x1 dq 12.0
x2 dq 14.0
rezultat dq ?
.code
start:
mov ax,@data
mov ds,ax

mov ax,40h
mov es,ax
mov ax,es:10h

test ax,2
jnz Instalat
mov dx,offset NuInstalat
mov ah,9
int 21h
jmp final
Instalat:
FINIT
FLDC ctrl_87

FLD x1 ;st(0)=x1
FLD x2 ;st(1)=x1 si st(0)=x2
FMUL
FST rezultat ;rezultat=st(0)
final:
mov ax,4c00h
int 21h
end

În continuare se prezintă un program care calculează


valoarea expresiei:
574

n xk
ex  
= k=1 k!

Transferul datelor către procedură se va face prin referinţă. Stiva


coprocesorului va arăta astfel:

ST(0 Va fi folosit pentru manevra


)
ST(1 Va conţine xk
)
ST(2 Va conţine k!
)
ST(3
)
n xk
ST(4 Va  
) conţine k=1 k!

ST(5
)
ST(6
)
ST(7
)
.model small
.stack 512h
.data
nnr dw 10
xnr dd 5
temp dw ?
rez dd ?
.code
e_la_x proc near
fld xnr ; incarca x din memorie
575
fst st(1) ; pune x in st1
fld1 ; pune 1 in st
fst st(2) ; initializeaza k! cu 1
fst st(4) ; initializeaza suma cu 1
mov cx,1
et: fnop
fld xnr
fmul st(1),st ; x la k

fxch st(2) ; schimba st cu st2


mov temp,cx
fimul temp ; k!
fxch st(2) ; schimba st cu st2
fld st(1) ; incarca in st pe x la k
fdiv st,st(2) ; face impartirea
fadd st(4),st ; aduna rezultatul fractiei la suma
din st4
inc cx
cmp cx,nnr ; compara cx cu n
jle et ; if <= sare la et
fxch st(4) ; aduce rezultatul in st
fst rez ; duce rezultatul in memorie
ret
e_la_x endp
start:
mov ax,@data
mov ds,ax ; incarca segmentul de date
xor cx,cx
call e_la_x ; apeleaza procedura
mov ax,4c00h ; functia de iesire
int 21h
end start

14.7 Concluzii

Trebuie realizat un echilibru între utilizarea resurselor coprocesorului şi


celelalte tipuri de aritmetici, aşa fel încât să se obţină precizia rezultatelor şi în
acelaşi timp un număr cât mai mic de cicluri maşină la execuţia programului.
576

17
PROGRAMARE MIXTĂ: C – LIMBAJ DE ASAMBLARE

17.1 Programul principal este scris în limbaj de asamblare, procedurile


apelate sunt scrise în limbajul C

Variabilele transmise procedurii apelate se definesc cu atributul extrn.


Procedurile apelate se definesc, de asemenea, cu atributul extrn. Se
folosesc pentru apel proceduri scrise în limbajul C pentru a realiza
funcţii de intrare/ieşire care presupun conversii.
Programul ASM1 adună trei numere. Se apelează funcţia scanf()
pentru a iniţializa trei variabile a, b, c. După însumare se apelează
procedura printf() pentru a imprima variabila suma. Se definesc în
programul C variabilele de tip char: format1, format2, format3, format4.

;ASM 1
.model small
.stack 100h
.data
; date externe
extrn c format1:word
extrn c format2:word
extrn c format3:word
extrn c format4:word
extrn c format:word
extrn c a:word
extrn c b:word
extrn c c:word
;date locale
suma dw ?
.code
public _main

; functiile externe apelate


extrn c printf:proc
extrn c scanf:proc
extrn c sablon:proc
extrn c refac:proc

; intrarea in program
_main proc

; apel functie C de formatarea a ecranului


577
call sablon c

; citirea parametrilor de la tastatura


lea ax,format1
call printf c,ax
lea ax,format
lea bx,a
call scanf c,ax,bx

lea ax,format2
call printf c,ax
lea ax,format
lea bx,b
call scanf c,ax,bx

lea ax,format3
call printf c,ax
lea ax,format
lea bx,c
call scanf c,ax,bx

; insumarea celor trei valori


mov ax,a
add ax,b
add ax,c
mov suma,ax

; afisarea rezultatului
lea ax,format4
call printf c,ax,suma

; apel functie C de refacere a ecranului


call refac c

; iesirea din program


ret
_main endp
end

//C1
#include <stdio.h>
#include <conio.h>
char format1[20]="a=";
char format2[20]="b=";
char format3[20]="c=";
char format4[20]="\nsuma=%d\n";
char format[20]="%d";
int a,b,c;
void sablon(void)
{
int i;
clrscr();
578
for(i=0;i<80;i++)
printf("-");
printf("Introduceti datele:\n");
for(i=0;i<80;i++)
printf("-");
printf("\n\n\n");
for(i=0;i<80;i++)
printf("-");
printf("\n");
for(i=0;i<80;i++)
printf("-");
gotoxy(1,4);
}

void refac(void)
{
getch();
clrscr();
}

Programul ASM2 compară două şiruri de caractere. Se citesc şirurile


apelând întreruperea 21h cu funcţia 01h. Pentru comparare se
apelează funcţia stringcmp() scrisă în limbajul C. Rezultatul returnat
este interpretat în programul scris în limbaj de asamblare.

;ASM 2
.model small
.stack 100h
.data
sir db 'parola',0
sirc db 10 dup(?)
m1 db 10,'Siruri identice',0
m2 db 10,'Siruri diferite',0
m3 db 10,'Dati sirul:',0
.code
public _main
extrn c printf:proc
extrn _stringcmp:proc
_main proc
lea ax,m3
call printf c,ax
xor bx,bx
citire:
mov ah,01h
int 21h
cmp al,0dh
je afisare
mov sirc[bx],al
inc bx
jmp citire
579
afisare:
mov al,0
mov sirc[bx],al
lea ax,sir
lea bx,sirc
push ax
push bx
call _stringcmp
add sp,4
test ax,ax
jnz diferite
lea ax,m1
call printf c,ax
jmp sfarsit
diferite:
lea ax,m2
call printf c,ax
sfarsit:
ret
_main endp
end

//C2
int stringcmp(char* x,char* y)
{
while(*x==*y)
{
if(*x=='\0')
return 0;
x++;
y++;
}
return *x-*y;
}

Programul ASM3 concatenează două şiruri de caractere. Se citesc


şirurile şi se apelează funcţia stringcat() care primeşte pe stivă
adresele celor două variabile, sir1, sir2. Adresa şirului rezultat este
returnată în registrul AX (funcţia returnează pointer spre char).

;ASM 3
.model small
.stack 100h
.data
sir1 db 'mergem la ',0,20 dup(?)
sir2 db 'stadion',0
format db 'Sirul concatenat este : %s',10,13,0
.code
public _main
extrn c printf:proc
580
extrn _stringcat:proc
_main proc
lea ax,sir1
lea bx,sir2
push bx
push ax
call _stringcat
add sp,4
lea bx,format
call printf c,bx,ax
ret
_main endp
end

//C3
#include <conio.h>
char* stringcat(char* s1,const char* s2)
{
char* t=s1;
while(*s1)
s1++;
while(*s1++=*s2++);
return t;
}
char* stringcat(char* s1,char* s2)
{
int i;
for(i=0;s1[i];i++);
for(;s1[i++]=*s2++;);
return s1;
}

Programul ASM4 afişează minimul sau maximul dintre două numere.


Se transmit funcţiei scrise în limbajul C numerele şi opţiunea de calcul
a valorii minime (1) sau maxime (2).

;ASM 4
.model small
.stack 100h
.data
format1 db 'Introduceti a,b,optiune(1-min,2-max):',0
format2 db '%d%d%d',0
format3 db 'Functia este: %d',10,13,0
a dw ?
b dw ?
x dw ?
pf dw ?
.code
public _main
extrn _funct:proc
extrn c printf:proc
581
extrn c scanf:proc
_main proc
lea ax,format1
call printf c,ax
lea ax,format2
lea bx,a
lea cx,b
lea dx,x
call scanf c,ax,bx,cx,dx
mov ax,x
push ax
mov dx,1
call _funct
add sp,2
mov pf,ax
call pf c,a,b
lea bx,format3
call printf c,bx,ax
ret
_main endp
end

//C4
int min(int a,int b)
{
return a<b?a:b;
}
int max(int a,int b)
{
return a>b?a:b;
}
int (*funct(int x))()
{
int (*pfunct)();
switch(x)
{
case 1:
pfunct=min;
break;
case 2:
pfunct=max;
break;
}
return pfunct;
}

Programul ASM5 stabileşte şi afişează maximul dintre trei numere.

;ASM 5
.model small
mscanf macro par1,par2
582
lea ax,par1
lea bx,par2
call scanf c,ax,bx
endm
mprintf macro par
lea ax,par
call printf c,ax
endm
.stack 100h
.data
format1 db "Maxim = %d",10,13,0
format2 db "a=",0
format3 db "b=",0
format4 db "c=",0
format5 db "%d",0
a dw ?
b dw ?
c dw ?
rez dw ?
.code
public _main
extrn _max:proc
extrn c scanf:proc
extrn c printf:proc
_main proc
mprintf format2
mscanf format5,a
mprintf format3
mscanf format5,b
mprintf format4
mscanf format5,c
push c
push b
push a
call _max
add sp,6
lea bx,format1
call printf c,bx,ax
ret
_main endp
end

//C5
int max(int a,int b,int c)
{
return a>b?(a>c?a:c):(b>c?b:c);
}

17.2 Programul principal este scris în limbajul C, procedurile apelate sunt


scrise în limbaj de asamblare
583

Programul C6 citeşte un vector de numere întregi şi îl afişează pagină


cu pagină. Afişarea se face cu ajutorul funcţiei afis() definită în limbaj
de asamblare; aceasta apelează la rândul ei procedurile pozit() şi
sterg().

//C6
#include <stdio.h>
#include <conio.h>
extern void afis(void);
void main(void)
{
float v,x[40];
int i,n;
printf("n=");
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("x[%d]=",i);
scanf("%f",&v);
x[i]=v;
}
clrscr();
for(i=0;i<n;i++)
if((i%23==0)&&(i!=0))
{
afis();
printf("\nx[%d]=%f]",i,x[i]);
}
else
printf("\nx[%d]=%8.2f",i,x[i]);
afis();
}

;ASM 6
; Afisarea unui mesaj, asteptarea apasarii unei taste
; si stergerea ecranului
.model small
.data
sir db 'Pentru a continua apasati orice tasta...','$'
.code
public _afis ; Interfata cu programul C
pozit proc ; Pozitionarea pe linia 24,
coloana 1
mov ah,02h ; Functia BIOS
mov dh,24 ; Linia
mov dl,1 ; Coloana
int 10h ; Apelul intr BIOS 10h (functii grafice)
ret
pozit endp
584
sterg proc ; Stergerea ecranului
mov ah,0
mov al,2
int 10h
ret
sterg endp
_afis proc
call pozit ; Pozitionare pe ecran
mov ax,seg sir ; Afisare mesaj
mov ds,ax
mov dx,offset sir
mov ah,09h
int 21h ; Apel functie DOS afisare text
terminat cu '$'
mov ah,08h ; Asteptarea apasarii unei taste
int 21h
call sterg
ret ; Iesirea catre functia apelatoare
_afis endp
end

Programul C7 calculează suma a trei numere citite de la tastatură.


Pentru efectuarea adunării se apelează funcţia scrisă în limbaj de
asamblare suma(). Parametrii sunt transmişi prin valoare, pe stivă.

//C7
#include <stdio.h>
extern int suma(int,int,int);
void main(void)
{
int a,b,c;
printf("a=");
scanf("%d",&a);
printf("b=");
scanf("%d",&b);
printf("c=");
scanf("%d",&c);
printf("suma=%d\n",suma(a,b,c));
}

;ASM 7
.model small
.code
public _suma
_suma proc
push bp ; Salvarea registrului bp
mov bp,sp ; bp<-sp
mov ax,[bp+4] ; ax=par1
add ax,[bp+6] ; ax+=par2
add ax,[bp+8] ; ax+=par3
585
pop bp ; Refacere bp
ret ; Iesirea din functie
_suma endp
end

Spre deosebire de programul precedent, programul C8 care


efectuează aceleaşi calcule, transmite parametrii prin adresă, pe stivă.

//C8
#include <stdio.h>
extern int suma(int*,int*,int*);
void main(void)
{
int *pa,a,*pb,b,*pc,c;
printf("a=");scanf("%d",&a);
printf("b=");scanf("%d",&b);
printf("c=");scanf("%d",&c);
pa=&a;pb=&b;pc=&c;
printf("suma=%d\n",suma(pa,pb,pc));
}

;ASM 8
.model small
.stack 100h
.code
public _suma
_suma proc
push bp ; Salvare bp
mov bp,sp ; bp<-sp
mov di,[bp+4] ; di=pa
mov ax,[di] ; ax=*pa;
mov di,[bp+6] ; di=pb
add ax,[di] ; ax+=*pb
mov di,[bp+8] ; di=pc
add ax,[di] ; ax+=*pc
pop bp ; Refacere bp
ret ; Iesire din functie
_suma endp
end

Programul C9 exemplifică returnarea rezultatului pe stivă, prin adresă.

//C9
#include <stdio.h>
extern int* suma(int*,int*,int*);
void main(void)
{
int *pa,a,*pb,b,*pc,c,*ps;
printf("a=");
scanf("%d",&a);
586
printf("b=");
scanf("%d",&b);
printf("c=");
scanf("%d",&c);
pa=&a;
pb=&b;
pc=&c;
ps=suma(pa,pb,pc);
printf("suma=%d\n",*ps);
}

;ASM 9
.model small
.stack 100h
.data
rez dw ?
.code
public _suma
_suma proc
push bp
mov bp,sp
mov di,[bp+4]
mov ax,[di]
mov di,[bp+6]
add ax,[di]
mov di,[bp+8]
add ax,[di] ; ax=*par1+*par2+*par3
mov rez,ax ; rez<-ax
mov ax,offset rez ; ax<-&rez
pop bp
ret
_suma endp
end

Programul C10 calculează şi afişează suma elementelor unui vector.


Parametrii transmişi pe stivă sunt numărul de elemente al vectorului
şi adresa primului element.

//C10
#include <stdio.h>
extern int suma(int*,int);
void main(void)
{
int v,x[40];
int i,n;
printf("n=");
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("x[%d]=",i);
587
scanf("%d",&v);
x[i]=v;
}
printf("Suma elementelor vectorului = %d",suma(x,n));
}

;ASM 10
.model small
.stack 100h
.code
public _suma
_suma proc
push bp
mov bp,sp
mov cx,[bp+6] ; cx <- numarul de elemente
mov si,[bp+4] ; si <- adresa primului element
xor ax,ax ; ax=0
ciclu: ; do{
add ax,[si] ; ax+=x[i];
inc si ; i++
inc si
loop ciclu ; }while(i<n)
pop bp
ret
_suma endp
end

//C11
/*
Produsul scalar a doi vectori.
*/
#include <stdio.h>
extern int prodscalar(int*,int*,int);
void main(void)
{
int x[40],y[40],v,i,n;
printf("n=");
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("x[%d]=",i);
scanf("%d",&v);
x[i]=v;
printf("y[%d]=",i);
scanf("%d",&v);
y[i]=v;
}
v=prodscalar(x,y,n);
printf("Produsul scalar = %d\n",v);
}
588
;ASM 11
; Produsul scalar a doi vectori
.model small
.stack 100h
.code
public _prodscalar
_prodscalar proc
push bp
mov bp,sp
mov cx,[bp+8] ; Numarul de elemente
mov si,[bp+4] ; Adresa primului vector
mov di,[bp+6] ; Adresa celui de-al doilea vector
xor bx,bx
ciclu: ; Bucla repetata de cx ori
mov ax,[si] ; bx+=[si]*[di];
mul word ptr [di]
add bx,ax
inc si
inc si
inc di
inc di
loop ciclu
mov ax,bx ; ax=bx (rezultatul)
pop bp
ret
_prodscalar endp
end

//C12
/*
Suma elementelor unei matrice.
*/
#include <stdio.h>
#define N 5
extern int suma(int x[][N],int m,int n);
void main(void)
{
int x[3][N],v;
int i,j,m,n;
printf("numarul de linii = ");
scanf("%d",&m);
printf("numarul de coloane = ");
scanf("%d",&n);
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
printf("x[%d][%d]=",i,j);
scanf("%d",&v);
x[i][j]=v;
}
printf("suma elementelor matricii = %d\n",suma(x,m,n));
589
}

;ASM 12
; Suma elementelor unei matrici
.model small
.stack 100h
.code
c equ 5 ; Numarul de coloane al matricii
public _suma
_suma proc
push bp
mov bp,sp
mov bx,[bp+4] ; Adresa primului element
xor ax,ax ; ax=0
mov dx,[bp+6] ; Numarul de linii
c1:
mov cx,[bp+8] ; Numarul de coloane
xor si,si ; si=0
c2:
add ax,[bx][si] ; ax+=[bx][si]
inc si ; Urmatorul element
inc si
loop c2 ; Mai sunt coloane ?
add bx,c*2 ; Urmatoarea linie
dec dx
jnz c1 ; Mai sunt linii ?
pop bp
ret
_suma endp
end

//C13
/*
Realizarea unei functii specificate de utilizator (min
sau max) asupra a doua numere introduse de la consola.
Se realizeaza cu aplelu unei functii asm care primeste
ca parametru
functia dorita.
*/
#include <stdio.h>
extern int suma(int*,int*,int(*)(int,int),int);
int max(int a,int b)
{
return a>b?a:b;
}
void main(void)
{
int x[40],y[40],n,i,v;
printf("n=");
scanf("%d",&n);
for(i=0;i<n;i++)
590
{
printf("x[%d]=",i);
scanf("%d",&v);
x[i]=v;
printf("y[%d]=",i);
scanf("%d",&v);
y[i]=v;
}
printf("Suma elementelor maxime = %d\n",suma(x,y,max,n));
}

;ASM 13
; Suma elementelor maxime din doi vectori de elemente
; Functia de comparare e transmisa ca parametru
; (o adresa near)
.model small
.stack 100h
.data
rez dw 0
.code
public _suma
_suma proc
push bp
mov bp,sp
mov cx,[bp+10] ; Numarul de elemente
mov di,[bp+6] ; Adresa celui de-al doilea vector
mov si,[bp+4] ; Adresa primului vector
ciclu:
mov ax,[si]
mov bx,[di]
call word ptr [bp+8] c,ax,bx ; Apel functie de comparare
add rez,ax ; rez+=max
inc si ; Urmatoarele elemente din vectori
inc si
inc di
inc di
loop ciclu
mov ax,rez ; ax<-rez
pop bp
ret
_suma endp
end

Celelalte programe realizează prelucrări care evidenţiază modalităţi de


dezvoltare software conform programării mixte.
În cazul în care se doreşte gestionarea strictă a ciclurilor maşină în
secvenţe de program cu structuri repetitive imbricate în care numărul
de referiri ale unei secvenţe este foarte mare, se preferă dezvoltarea
unui program mixt în care secvenţa referită este scrisă direct în limbaj
de asamblare. De exemplu, secvenţa:
591

...
s=0;
for (i=0; i<30000; i++)
for (j=0; j<70000; j++)
for (k=0; k<20000; k++)
if(i= =j)
s+=k*2;
else
s+=k/2;
...

se transformă în secvenţa mai performantă de mai jos:


...
s=0;
for (i=0; i<30000; i++)
for (j=0; j<70000; j++)
for (k=0; k<20000; k++)
{
asm mov bx, k
asm mov ax, i
asm cmp ax, j
asm jz egal
asm shr bx
asm egal: nop
asm shl bx
asm mov ax, s
asm add ax, bx
asm mov s, ax
}
...

Blocul instrucţiunilor limbajului de asamblare se


delimitează cu atributul asm, obţinându-se secvenţa echivalentă

asm {
mov bx, k
mov ax, i
cmp ax, j
jz egal
shr bx
egal: nop
shl bx
mov ax, s
add ax, bx
mov s, ax
}

17.3 Concluzii
592

Programarea mixtă are ca obiective atât creşterea performanţei


programelor în execuţie, cât şi reutilizarea de software verificat în
practică, din diferite generaţii, scris în limbaje de programare mai
puţin utilizate în prezent.
De asemenea prin programarea mixtă se integrează programele
scrise în orice limbaj de programare formând un produs software
complex.
593

18
DEZVOLTAREA DE APLICAŢII ORIENTATE
OBIECT IN LIMBAJ DE ASAMBLARE

18.1. Concepte folosite în dezvoltarea programelor orientate obiect

O clasă este definită ca abstractizare a trăsăturilor esenţiale ale unei colecţii


de obiecte înrudite. Deci o clasă este o mulţime de obiecte care partajează o
structură comună şi un comportament comun. Se extinde astfel conceptul datelor
de tip structură, combinându-se datele cu codul ce le procesează.
O clasă are două componente, una statică şi alta dinamică. Prima
componentă se referă la proprietăţile obiectelor caracterizate de clasă, şi este
reprezentată de datele membre (câmpurile cu nume care posedă valoare şi care
caracterizează starea obiectului în timpul execuţiei). A doua se referă la
comportamentul obiectelor clasei, deci la modul în care acţionează şi reacţionează
acestea, şi este constituită din metodele clasei respective.
Un obiect este definit ca materializarea sau concretizarea tipologiei descrise
de o anumită clasă. El este un concept, abstractizare sau lucru cu limite precizate şi
cu înţeles pentru problema în cauză.

Clasa

Obiect1 Obiect2

Figura 18.1 – Relaţia dintre o clasă şi obiectele ei

Relaţia dintre o clasă şi obiectele acesteia este echivalentă relaţiei dintre o


variabilă şi tipul acesteia, subliniindu-se astfel ideea că obiectele şi clasa ce le
defineşte nu sunt altceva decât un model mai avansat de variabile şi o extensie a
tipurilor fundamentale specifice fiecărui limbaj. Fiecare obiect preia şablonul
impus de clasa de care aparţine, şablon ce descrie caracteristicile sale (componenta
statică, reprezentată de date), precum şi operaţiile ce sunt permise asupra acestora
(interfaţa obiectului, descrisă prin metodele clasei). Operaţia de creare a unui obiect
din informaţiile exprimate de o clasă, se numeşte instanţiere, iar operaţia inversă
594

(definirea unei clase pornind de la o mulţime de obiecte) poartă numele de


clasificare sau tipizare.
Prin intermediul metodelor, orice obiect este o entitate dinamică care poate
fi creată, utilizată şi distrusă. Ca metode cu semnificaţie deosebită, atrag atenţia
constructorii şi destructorii. Aşa cum ne sugerează şi denumirea, constructorii
sunt acele operaţii apelate pentru crearea obiectelor şi iniţializarea stării lor, în timp
ce destructorii realizează operaţia inversă, ştergând spaţiul de memorie ocupat de
metodele şi datele membre ale acestora.
Acest mod de organizare a datelor şi a metodelor, de manipulare a acestora,
face apel la o serie de concepte precum abstractizarea, moştenirea şi
polomorfismul.
Abstractizarea – reprezintă procesul de ignorare intenţionată a detaliilor
nesemnificative şi reţinerea proprietăţilor definitorii ale unei entităţi. Astfel, se
ascund detaliile de implementare, dezvăluindu-se, prin intermediul interfeţei
obiectului, comportarea esenţială a acestuia relativ la viziunea exterioară pe care
vrea să o imprime creatorul clasei.
Încapsularea – este mijlocul de separare a informaţiilor de manipulare a
unei entităţi (aspectele externe, accesibile utilizatorului acesteia) de informaţiile de
implementare. Ea poate fi întâlnită şi în alte tehnici de programare cum este, spre
exemplu, modularizarea programelor prin folosirea bibliotecilor. Prin intermediul
acesteia se realizează protejarea anumitor date membre ale obiectelor împotriva
distrugerii lor accidentale, ajungându-se în acest fel la o robusteţe ridicată a
programelor în implementarea orientată obiect comparativ cu cea tradiţională.
Moştenirea – se defineşte ca fiind o relaţie între clase prin intermediul
căreia o clasă partajează structura şi comportamentul definite de una sau mai multe
clase. În funcţie de numărul claselor de la se porneşte moştenirea, cunoscute sub
numele de clase de bază sau superclase, putem avea moştenire simplă (când
avem o singură clasă de bază) sau moştenire multiplă (când se folosesc
caracteristicile mai multor clase). Clasa rezultantă se numeşte subclasă sau clasă
derivată.
595

Structura clasei derivate


Clasa de Clasa de
baza 1 baza 2
C la sa
de
baza 1

C lasa
de
baza 2
Clasa
derivată Date şi
metode clasa
derivata . . . .

Figura 18.2 – Moştenirea multiplă

Polimorfismul – este capacitatea unei entităţi de a îmbrăca mai multe


forme. În programarea clasică, funcţiile se diferenţiază prin numele sub care sunt
cunoscute, parametri pe care îi acceptă, precum şi valoarea pe care o returnează. În
programarea orientată obiect compilatorul poate să facă diferenţa între funcţii şi
prin intermediul clasei de care aparţin.
Funcţii care au acelaşi nume, parametri şi valoare de retur în clasa de bază,
pot fi redefinite (supraîncărcate) în clasele derivate şi ajustate la noul context în
care sunt apelate. Acest tip de polimorfism poartă denumirea de polimorfism de
moştenire.
În cadrul polimorfismului ad-hoc, de care aparţine cel menţionat mai sus,
se regăseşte şi polimorfismul coerciziune care este caracteristic limbajelor de
programare care dispun de facilităţi de conversie internă între tipuri.
Un alt tip de polimorfism este şi polimorfismul parametric, dar care
apelând la capacitatea compilatoarelor tradiţionale de a face diferenţa dintre funcţii
cu nume şi valoare de retur identice, dar cu tip şi / sau număr de parametri diferit,
nu aparţine neapărat de programarea orientată obiect.
Alături de aceste concepte mai este adăugat şi cel de persistenţă a
obiectelor. Acesta se referă la timpul de viaţă al unui obiect, la capacitatea acestuia
de a permite salvarea pe un suport de memorie externă şi de reconstituire ulterioară
în memoria internă a calculatorului. Realizarea acestei cerinţe presupune
identificarea clasei obiectului din informaţiile salvate şi construirea dinamică (în
timpul execuţiei) a acestuia, acţiune ce necesită metode avansate de programare
cum ar fi RTTI (Run-Time Type Identification - identificarea tipului în timpul
execuţiei) şi folosirea metodelor virtuale.
596

Prin apelarea la aceste concepte, programarea orientată obiect aduce o serie


de avantaje:
 reutilizabilitate – se permite reutilizarea codului deja testat şi se creşte,
astfel, robusteţea programelor concomitent cu diminuarea duratei de
realizare a acestora;
 scăderea complexităţii – fragmentarea aplicaţiei în entităţi şi relaţii,
care au înţeles pentru utilizator, este o analiză convenţională şi o tehnică
de design, care duce la o diminuare semnificativă a complexităţii iniţiale
a problemei studiate.
 flexibilitate – prin intermediul moştenirii se permite adăugarea sau
extragerea de obiecte, fără schimbări profunde în logica aplicaţiei,
realizându-se astfel o extindere sau o acomodare a aplicaţiei cu minim
de efort.
 înţelegere mai uşoară – punerea în corespondenţă unu la unu a
obiectelor din structura aplicaţiei cu cele ale problemei studiate duce la
o înţelegere mai uşoară a fenomenelor atât de către utilizator cât şi de
către proiectant.
Toate aceste avantaje au ca rezultat o creştere a productivităţii şi a uşurinţei
în întreţinere a aplicaţiilor orientate obiect, ceea ce impune tehnica de elaborare a
programelor orientată obiect ca soluţia optimă pentru programatori şi proiectanţi de
sisteme.
Orice tip de dată elementară sau derivată şi orice mecanism de gestionare a
resurselor unui sistem de calcul se regăsesc mai întâi în descriptorii respectivi,
funcţii şi operaţii în limbaj de asamblare.
Dezvoltarea de aplicaţii orientate obiect face excepţie numai aparent de la
această cerinţă. Proprietăţile obiectelor sunt implementate mai întâi în limbajele
evoluate (cum ar fi C++, Pascal) şi corespondenţele lor în modulele obiect sunt
suplinite de proprietăţile şi funcţiile primare deja existente în limbajul de
asamblare.
În [SWAN95] se prezintă modalitatea de dezvoltare a aplicaţiilor orientate
obiect fără ca limbajul de asamblare să conţină atribuite, structuri specifice definirii
de clase şi manipulării de obiecte.
Pentru descrierea mai multor modalităţi de implementare a programării
orientate obiect în limbajul de asamblare, se va face apel la reprezentarea tipurilor
de date întregi şi complexe şi a metodelor de manipulare a acestora. S-a ales astfel
un exemplu simplu care să uşureze înţelegerea şi urmărirea codului prezentat,
tehnicile folosite fiind uşor de extins la orice aplicaţie reală.
597

18.2. Definirea obiectelor prin structuri

Una dintre cele mai simple metode de implementare a obiectelor în limbajul


de asamblare este folosirea regulilor de construcţie a structurilor simple, fără
folosirea unor construcţii specifice orientate obiect.
Se consideră o aplicaţie în care se manipulează numere întregi şi numere
complexe (s-au ales numerele întregi în locul celor reale pentru o simplificare a
codului). Se defineşte mai întâi o clasă care să încapsuleze comportamentul
numerelor întregi şi, derivată din aceasta, una care să definească comportamentul
numerelor complexe.
Pentru fiecare din cele două clase se definesc operaţiile de adunare şi de
înmulţire (cele de scădere şi de împărţire sunt asemănătoare). Pentru clasa Integer
se dezvoltă următoarele proceduri:

...
.code
adunare_integer proc
push ax
mov ax, [di].nr_int
add ax, word ptr [si]
mov [di].nr_int, ax
pop ax
retn
adunare_integer endp

inmultire_integer proc
push ax
mov ax, [di].nr_int
mul word ptr [si]
mov [di].nr_int, ax
pop ax
retn
inmultire_integer endp
...

Numărul implicat în adunare / înmulţire este trimis prin adresă prin registrul
SI, iar adresa obiectului este trimisă prin registrul DI.
Pentru clasa Complex procedurile sunt:

...
adunare_complex proc
push ax
mov ax, [di].nr_int
add ax, word ptr [si]
mov [di].nr_int, ax
mov ax, [di].nr_complex
598
add ax, word ptr [si+2]
mov [di].nr_complex, ax
pop ax
retn
adunare_complex endp

inmultire_complex proc
.data
int_temp dw 0
.code
push ax
mov ax, [di].nr_complex
mul word ptr [si+2]
mov bx, ax
mov ax, [di].nr_int
mul word ptr [si]
sub ax, bx
mov int_temp, ax
mov ax, [di].nr_complex
mul word ptr [si]
mov bx, ax
mov ax, [di].nr_int
mul word ptr [si+2]
add ax, bx
mov [di].nr_complex, ax
mov ax, int_temp
mov [di].nr_int, ax
pop ax
retn
inmultire_complex endp
...

Numărul complex este format din două date membre, nr_int şi nr_complex,
adunarea şi înmulţirea fiind definite astfel:

(a , a )  (b , b )  (a  b , a  b )
1 2 1 2 1 1 2 2
(a , a )  (b , b )  (a b  a b , a b  a b )
1 2 1 2 1 1 2 2 1 2 2 1

Transmiterea parametrilor se face prin adresa din registrul SI ([SI] = partea


întreagă de adunat / înmulţit, iar [SI+2] = partea imaginară), iar adresa obiectului se
trimite prin registrul DI.
După definirea procedurilor urmează definirea obiectelor:

...
.data
Integer struc
nr_int dw 0
adun_int dw adunare_integer
599
inm_int dw inmultire_integer
Integer ends

Complex struc
baza Integer <>
nr_complex dw 0
adun_compl dw adunare_complex
inm_compl dw inmultire_complex
Complex ends
...

Obiectele sunt definite ca nişte structuri obişnuite, având ca membri atât


datele cât şi pointeri spre metode. Obiectul Complex este derivat din obiectul
Integer prin definirea unei date membre de tip Integer (baza Integer <>).
Pentru rularea celor două clase se poate construi un cod de genul celui
următor:
...
ob_integer Integer <4>
ob_complex Complex <7,5>
numar1 dw 10
numar2 dw 5

.code
start:
mov ax,@data
mov ds,ax

lea si, numar1 ;[si] = numar1, [si+2] = numar2

lea di,ob_integer
call ob_integer.adun_int ;nr_int = 4+10 = 14
call ob_integer.inm_int ;nr_int = 14*10 = 140
lea di,ob_complex
call ob_complex.adun_compl ;nr_int = 7+10 = 17
;nr_complex = 5+5 = 10
call ob_complex.inm_compl ;nr_int = 17*10-10*5 = 120
;nr_complex = 17*5+10*10 = 185

sfarsit:
mov ax,4c00h
int 21h
end start

Instanţierea claselor se face ca în cazul structurilor obişnuite. Se observă că


înaintea apelurilor procedurilor este necesară încărcarea în registrele SI şi DI a
parametrilor şi a obiectelor asupra cărora se efectuează operaţia.
Pentru verificarea corectitudinii calculelor se încarcă programul executabil,
rezultat prin asamblarea codului de mai sus, în turbo debugger dacă se foloseşte
600

asamblorul oferit de firma Borland. Pentru aceasta trebuie ca programul să fie


asamblat şi link-editat cu următoarele comenzi:

tasm /zi <nume_program.asm>


tlink /v <nume_program.obj>

18.3. Definirea obiectelor prin macrodefiniţii

Limbajul de asamblare conţine facilităţi de definire a macrodefiniţiilor.


Macrodefiniţiile sunt construcţii realizate cu ajutorul unor cuvinte cheie al căror
efect constă într-un proces de expandare în textul programului sursă, permiţându-se
în acest mod definirea în mod simbolic a unor secvenţe de program (instrucţiuni,
definiţii de date, directive etc.), asociate cu un nume.
Macrodefiniţiile sunt structuri complexe în care se includ şi elemente ale
macroexpandării condiţionale, repetitive, recursive şi stringizare (vezi lucrările
[SOMN92], [MUS96]). Un aspect foarte important de care trebuie sa se ţină cont în
utilizarea macroinstrucţiunilor este faptul că ele sunt procesate într-o fază
preliminară asamblării.
Pentru definirea structurilor necesare modelării orientate obiect, precum şi a
moştenirii şi încapsulării datelor acestora, există şi varianta folosirii
macrodefiniţiilor. Macrodefiniţiile sunt deosebit de utile în realizarea programelor
orientate obiect, putând fi folosite construcţii speciale de verificare a restricţiilor
care să genereze eventualele erori nu în timpul execuţiei programului, ci în faza de
analizare sintactică a codului sursă, exact ca în limbajele de nivel înalt orientate
obiect.
În continuare se prezintă un model de implementare a obiectelor în limbajul
de asamblare, ce face apel la un sistem de simboluri variabile, construite prin
stringizare, folosite împreună cu elemente macrocondiţionale pentru verificarea
restricţiilor impuse de modelul obiectual. Sintaxa rezultată prezintă asemănări
evidente cu limbajele de nivel înalt orientate obiect, în special cu limbajul C++.
Mai întâi este necesară definirea unor date şi a unor variabile simbolice
folosite în cadrul macrodefiniţiilor (semnificaţia acestora va rezulta ulterior, pe
parcursul descrierilor din paginile următoare):
; V1.0
.data
currOffset dw 0 ;offsetul obiectului apelator pt. o
;functie din aceeasi clasa
;VARIABILE DE CONSTRUIRE (GENERALE)
currProt = 0 ;implicit public (folosita in
declaratiile ;claselor)
currId = 0 ;id-ul curent (id-ul primei clase = 1)
currClass = 0 ;(nici o clasa initial)
currApelator = 0 ;apelatorul curent al operatiei (id-ul
;obiectului)
601
Id = 1 ;folosita de OBJECTS
gasit = 0 ;folosita la cautarea in clase de baza
deplasament = 0 ;deplasamentul folosit in cazul ;accesarii
unor variabile din
;clasele de baza
baseId = 0 ;id-ul clasei de baza la cautarea datelor
;membre
publicDer = 1 ;indica daca derivarea se face
;public (=1) sau private (=0)
;VARIABILE CARE DEPIND DE CLASE (de numele clasei sau de id-
ul ei)
;<numeClasa>Id id-ul clasei cu numele <numeClasa>
;<numeObiect>Id id-ul obiectului (EGAL ;INTOTDEAUNA
CU ID-UL CLASEI
; DE CARE APARTINE!)
;no<Id> nr elementelor din clasa cu id-ul
<id>
;baseNo<Id> nr claselor de baza pentru ;clasa cu
id-ul <id>
;disable<Id>Prot indica daca se foloseste sau nu
;protectia datelor
; (folosita in apelul functiilor
;membre)
;friend<Id>Of<currId>Id indica, daca exista, prietenia ;intre
clasele cu
; id-urile <Id> si <curcId>
;depl<baseId>In<currId> indica deplasarea clasei de baza (
<baseId> ) in clasa
; cu id-ul <currId>

Pentru definirea unei clase se foloseşte macrodefiniţia class cu următoarea


sintaxă:
class <nume_clasa>
...
<nume_clasa> ends

Această macrodefiniţie încapsulează o structură standard, iniţiind şi o serie


de simboluri variabile ce vor fi folosite la identificarea clasei:
class MACRO nume
IFB <nume>
%OUT < **Error** Class name not specified ... >
.ERR
ELSE ;genereaza clasa
%OUT < Creating class nume ... >
currId = currId + 1 ;id-ul urmator va
fi +1
nume&Id = currId ;id-ul de clasa
;creaza variabila ce contine nr de elemente
setBaseAndNoToZero %currId
602
;creaza variabila de control a protectiei
;clasei
setDisableProt %currId, 0 ;initial protectia
este ;activata

nume struc ;creaza structura


ENDIF
ENDM

În cazul în care nu se precizează un nume pentru clasă se generează eroare.


În caz contrar acest nume va fi folosit pentru declararea structurii, nu înainte ca id-
ul clasei (format prin stringizare din numele clasei şi şirul de caractere “Id”) se fie
stabilit în mod secvenţial cu ajutorul variabilei simbolice <currId>. Astfel, toate
clasele vor avea un număr de identificare distinct (începând cu 1).
Macrodefiniţia class se foloseşte de alte două macrodefiniţii. Una dintre
aceste este setBaseAndNoToZero:

setBaseAndNoToZero MACRO id
baseNo&id = 0
No&Id = 0
ENDM

care se foloseşte de parametrul transmis (<id> = id-ul clasei) pentru a stabili


numărul de clase de bază (baseNo<id>) şi numărul de elemente (No<Id>) ale
clasei definite ca fiind zero.
Altă macrodefiniţie folosită este setDisableProt:

setDisableProt MACRO id, val


disable&id&Prot = val
ENDM

cu ajutorul căreia se creează o variabilă de protecţie a datelor ce va fi


folosită în macrodefiniţiile următoare (prin transmiterea parametrului val cu
valoarea 0 protecţia este activă).
Protecţia membrilor clasei se realizează cu ajutorul variabilei simbolice
<currProt> a cărei valoare se poate modifica cu macrodefiniţiile următoare:

public_ MACRO
currProt = 0
ENDM

private_ MACRO
currProt = 1
ENDM

Pentru declararea datelor membre avem următoarele macrodefiniţii:


603

date MACRO den, tip, v


internDate %currId, den, tip, <v>
ENDM
internDate MACRO id, den, tip, v
No&id = No&id + 1
;alocare data
IFB <v>
den&id&@val@ tip ?
ELSE
den&id&@val@ tip v
ENDIF
;alocare protectie
IFE currProt ;; public
%OUT < define den as public ... >
ELSE
%OUT < define den as private ... >
ENDIF
den&id&Prot = currProt
ENDM

Deci se foloseşte macrodefiniţia date cu următoarea sintaxă:

date nume_data, tip_standard, [valoare]

Cu ajutorul acestei macroinstrucţiuni se alocă o variabilă de un anumit tip


(precizat prin parametrul <tip>) care are un nume modificat (format prin
stringizare sub şablonul <den><id_clasa>@val@) faţă de cel indicat de utilizator)
cu scopul de restricţionare a accesului direct. Totodată se creşte numărul de
elemente ale clasei şi se defineşte o variabilă (<den><id_clasa>Prot) prin care se
va reţine protecţia asupra datei introduse.
Declararea metodelor membre se face în mod asemănător folosindu-se
sintaxa:

func nume_rutina

iar macrodefiniţiile folosite sunt:

func MACRO den


internFunc %currId, den
ENDM
internFunc MACRO id, den
No&id = No&id + 1
IFE currProt ;; public
%OUT < define function den as public ... >
ELSE
%OUT < define function den as private ... >
ENDIF
604
den&id&FuncProt = currProt
ENDM

Aceste macrodefiniţii nu fac altceva decât să crească numărul de elemente


ale clasei (No<id_clasa>) şi să stabilească protecţia pentru procedura respectivă
(<den><id_clasa>FuncProt). În cadrul structurii nu este necesară reţinerea vreunei
evidenţe a procedurii (pointer), rutina respectivă fiind identificată printr-un nume
ce depinde de identificatorul obiectelor clasei respective. Pentru definirea funcţiei
(după declararea claselor) se foloseşte următoarea sintaxă:

classFunc nume_clasa, nume_rutina


...
endFunc nume_clasa, nume_rutina

ce are ca suport următoarele macrodefiniţii:

classFunc MACRO ob,fun


incepProc %(ob&Id), fun
;scoate temporar protectia pt datele din aceeasi clasa
setDisableProt %(ob&Id), 1
ENDM
incepProc MACRO id, fun
currApelator = id ;folosit in functiile prietene
currId = id
fun&id&@func@ proc
ENDM
endFunc MACRO ob,fun
;repune protectia
setDisableProt %(ob&Id), 0
;se iese din functie
currApelator = 0
currId = 0
ret
sfarProc %(ob&Id), fun
ENDM
sfarProc MACRO id, fun
fun&id&@func@ endp
ENDM

Se începe prin stabilirea unor variabile simbolice pentru identificarea


obiectului în al cărui context se vor executa instrucţiunile din rutina
(<currApelator>, folosit pentru accesul la datele claselor pentru care clasa curentă
a fost declarată ca prietenă, şi <currId> pentru accesul la datele membre clasei).
După care se declară începutul procedurii (se remarcă numele schimbat al
procedurii: <denumirea_functiei><id_clasa>@func@) şi se întrerupe, pe
parcursul întregii rutine, protecţia datelor membre (deoarece se rulează în contextul
clasei) prin setarea pe 1 a variabilei disable<id_clasa>Prot. Sfârşitul procedurii, pe
605

lângă încheierea efectivă a rutinei, repune protecţia şi resetează variabilele


simbolice folosite la identificarea contextului.
Declararea unei clase ca prietene a alteia (cu acces complet asupra datelor
membre) se realizează prin definirea unei variabile simbolice cu ajutorul
macrodefiniţiei friend:

friend MACRO clase


IRP cl,clase
friend2 <cl>
%OUT < Class cl became friend of current class ... >
ENDM
ENDM
friend2 MACRO cl
friend3 %(cl&Id), %currId
ENDM
friend3 MACRO id, currId
friend&id&of&currId = 1 ;defineste variabila
;de prietenie
ENDM

Pentru moştenire simplă sau multiplă, s-au folosit următoarele


macrodefiniţii (moştenirea este realizată doar pentru un singur nivel, dar poate fi
extinsă prin folosirea de macrodefiniţii recursive):

publicExt MACRO b
IRP den,b
publicDer = 1 ;mostenire publica
makeBaseName1 %currId, den
%OUT < Current class is derived from den ... >
ENDM
ENDM
privateExt MACRO b
IRP den,b
publicDer = 0 ;mostenire private
makeBaseName1 %currId, den
%OUT < Current class is derived from den ... >
ENDM
ENDM
makeBaseName1 MACRO currId, den
baseNo&currId = baseNo&currId + 1
makeBaseId currId, %(baseNo&currId), %(den&Id)
makeBaseName2 %(baseNo&currId), currId, %(den&Id), den
ENDM
;macro-uri interne pentru extend
makeBaseId MACRO currId, currBaseNo, baseId ;creeaza
;variabila id a bazei
baseNo&currBaseNo&Of&currId&Id = baseId
ENDM
makeBaseName2 MACRO currBaseNo, currId, baseId, den
606
;aloca obiectul de baza
baseNo&currBaseNo&@base@ den <>
;calculeaza deplasamentul fata de clasa derivata
depl&baseId&In&currId = OFFSET baseNo&currBaseNo&@base@
;mostenire publica
IF publicDer
;declara clasa derivata ca prietena a clasei de baza
friend3 currId, baseId
ENDIF
ENDM

Se creşte mai întâi numărul de clase de bază ale clasei curente


(baseNo<id_clasa>), apoi se trece la definirea unei variabile de identificare a clasei
de bază (baseNo<nr_baza>Of<id_clasa> = <id_baza>) şi la declararea efectivă a
structurii clasei de bază în cea derivată (cu numele schimbat
baseNo<nr_baza>@base@) şi a deplasării acesteia în cadrul clasei derivate
(depl<id_baza>In<id_clasa>). Pentru accesul la toate datele clasei de bază (în
cazul moştenirii publice) se face apel la un artificiu: se declară clasa derivată ca o
clasă prietenă a clasei de bază.
După cum s-a arătat, datele membre ale unei clase au numele modificat şi
deci necunoscut pentru utilizator. Pentru accesul la acestea, prin verificarea
protecţiei şi căutarea în clasele de bază, s-au construit următoarele macrodefiniţii:

set MACRO ob, camp, v


;verifica daca suntem intr-o functie membra (nu se mai
specifica
; obiectul)
IFB <v>
operStanga movToOb, %currId, currOffset, ob, camp
ELSE
mov currOffSet,OFFSET ob
operStanga movToOb, %(ob&Id), currOffset, camp, v
ENDIF
IFE gasit ;nu s-a gasit
%OUT < **Error** camp is not a member of ob ... >
.ERR
ELSE
gasit = 0 ;pt. urmatoarele cautari se revine
;la 0
ENDIF
deplasament = 0
ENDM
get MACRO unde, ob, camp
;verifica daca suntem intr-o functie membra (nu se mai
;specifica
; obiectul)
IFB <camp>
operDreapta movFromOb, unde, %currId, currOffset, ob
ELSE
607
mov currOffSet,OFFSET ob
operDreapta movFromOb, unde, %(ob&Id), currOffSet, camp
ENDIF
IFE gasit ;nu s-a gasit
%OUT < **Error** camp is not a member of ob ... >
.ERR
ELSE
gasit = 0 ;pt. urmatoarele cautari se revine
;la 0
ENDIF
deplasament = 0
ENDM
operStanga MACRO oper, id, ob, camp, v
IFDEF camp&id&@val@ ;se afla direct in ;clasa?
;s-a gasit si se face operatia in functie de protectie
isProt? id, camp
oper id, ob, camp, v
gasit = 1 ;l-a gasit
ELSE ;NU, poate se afla intr-o
;clasa de baza
;CAUTARE IN CLASELE DE BAZA:
searchInBase %(baseNo&id), id, camp
IF gasit
;se introduce operatia propriu-zisa
;baseId va contine id-ul la care s-a ajuns in
;cautare
;si a carui clasa cuprinde variabila
isProt? %baseId, camp ;vede daca este
;protejata
;in clasa derivata
oper %baseId, ob, camp, v

ENDIF
ENDIF
ENDM
operDreapta MACRO oper, unde, id, ob, camp
IFDEF camp&id&@val@ ;se afla direct in ;clasa?
;s-a gasit si se face operatia in functie de protectie
isProt? id, camp
oper unde, id, ob, camp

gasit = 1 ;l-a gasit


ELSE ;NU, poate se afla intr-o
;clasa de baza
;CAUTARE IN CLASELE DE BAZA:
searchInBase %(baseNo&id), id, camp
IF gasit
;se introduce operatia propriu-zisa
;baseId va contine id-ul la care s-a ajuns in
;cautare
;si a carui clasa cuprinde variabila
608
isProt? %baseId, camp ;vede daca este
;protejata
;in clasa derivata
oper unde, %baseId, ob, camp
ENDIF
ENDIF
ENDM
movToOb MACRO id, ob, camp, v
push bx
mov bx,word ptr ob
mov DS:[bx][deplasament].&camp&id&@val@, v
pop bx
endm
movFromOb MACRO unde, id, ob, camp
push bx
mov bx,word ptr ob
mov unde, DS:[BX][deplasament].&camp&id&@val@
pop bx
endm

Aceste macrodefiniţii, în cazul găsirii variabilelor, verifică protecţia


acestora cu ajutorul macroinstrucţiunii:

isProt? MACRO id, den


isProt?2 id, den, %currApelator
ENDM
isProt?2 MACRO id, den, currApelator
;verifica daca suntem intr-o functie a unei clase
;prietene
IFNDEF friend&currApelator&of&id
;verifica daca se folosesc protectiile
IFE disable&id&Prot
IF den&id&Prot
%OUT < **Error** Member &den is not accesible ... >
.ERR
ENDIF
ENDIF
ENDIF
ENDM

În cazul negăsirii variabilei în clasa curentă se realizează căutarea ei în


primul nivel de derivare cu ajutorul macrodefiniţiei:

searchInBase MACRO no, id, camp


noCurr = 1
REPT no ;cauta in toate clasele de baza
searchInBase2 %noCurr, id, camp
IF gasit ;l-a gasit si op. e deja
;introdusa
EXITM ;se iese din macrourile ;recursive
609
ENDIF
noCurr = noCurr + 1
ENDM
ENDM
searchInBase2 MACRO noCurr, currId, camp
baseId = baseNo&noCurr&Of&currId&Id
searchInBase3 currId, %baseId, camp
ENDM
searchInBase3 MACRO currId, baseId, camp
searchInBase4 camp&baseId&@val@
IF gasit ;la gasit si se calculeaza deplasamentul in
;functie de baseId
addDeplas %(depl&baseId&In&currId)
ENDIF
ENDM
searchInBase4 MACRO den
IFDEF den ;data cu denumirea den a fost
;definita
gasit = 1
ENDIF
ENDM
addDeplas MACRO deplCurr
deplasament = deplasament + deplCurr
ENDM

Aceste macrodefiniţii, în cazul găsirii variabilei, setează <gasit> pe 1 şi


încarcă <baseId> cu identificatorul clasei în care aceasta a fost găsită. Totodată se
calculează şi deplasamentul clasei de bază în derivată (folosit de funcţiile de acces
la membru).
Pentru apelul procedurilor membre mecanismul este acelaşi, chiar
simplificându-se datorită necalculării deplasamentului (pentru apel s-au construit
macrodefiniţii run, isFuncProt?, searchFuncInBase). De asemenea, pentru
accesul la datele membre se pot imagina şi funcţii mai complicate, cum ar fi aflarea
adresei sau alte operaţii directe fără preluarea conţinutului într-un registru.
Pentru exemplificarea utilizării acestor macrodefiniţii se va folosi exemplul
numerelor reale şi complexe prezentat în prima variantă de implementare:

.model large
include object.asm
.stack 100h

class Integer
public_
date nr_int, dw, 0 ;partea intreaga
func Adunare
func Inmultire
Integer ends

class Complex
610
publicExt <Integer>
public_
date nr_complex, dw, 0 ;partea complexa
func Adunare
func Inmultire
Complex ends

.code
;DEFINIREA METODELOR
classFunc Integer, Adunare
push ax
get ax, nr_int
add ax, word ptr [si]
set nr_int, ax
pop ax
endFunc Integer, Adunare

classFunc Integer, Inmultire


push ax
get ax, nr_int
mul word ptr [si]
set nr_int, ax
pop ax
endFunc Integer, Inmultire

classFunc Complex, Adunare


push ax
get ax, nr_int
add ax, word ptr [si]
set nr_int, ax
get ax, nr_complex
add ax, word ptr [di]
set nr_complex, ax
pop ax
endFunc Complex, Adunare

classFunc Complex, Inmultire


push ax
push bx
get ax, nr_complex
mul word ptr [di]
mov bx, ax
get ax, nr_int
mul word ptr [si]
sub ax, bx
push ax
get ax, nr_complex
mul word ptr [si]
mov bx, ax
get ax, nr_int
mul word ptr [di]
add ax, bx
611
set nr_complex, ax
pop ax
set nr_int, ax
pop bx
pop ax
endFunc Complex, Inmultire

Se observă modul de definire al claselor asemănător celui existent în


limbajul C++. Clasa Complex este moştenită din clasa Integer (publicext
<Integer>) ceea ce îi asigură şi partea întreagă.
Numele procedurilor nu mai trebuie să fie diferit ca în exemplul anterior
(deoarece numele procedurilor este format şi din identificatorul clasei),
polimorfismul fiind realizat prin căutarea mai întâi în clasa curentă a procedurii şi
apoi în cele de bază. În cadrul procedurilor accesul la datele membre se face prin
funcţiile get şi set (obiectul nu mai trebuie precizat, se ia obiectul în contextul
căruia are loc execuţia). Parametrii se trimit prin adresă prin registrele SI şi DI.
Adresa obiectului nu mai trebuie trimisă ca în varianta anterioară, macrodefiniţiile
folosindu-se de identificatorii obiectelor pentru a identifica structurile şi funcţiile
corespunzătoare.
Pentru testare se poate folosi următoarea sursă:

.data
define ob_integer, Integer
define ob_complex, Complex
numar1 dw 10
numar2 dw 5

.code
start:
mov ax,@data
mov ds,ax

set ob_integer, nr_int, 4


set ob_complex, nr_int, 7
set ob_complex, nr_complex, 5

lea si, numar1


run ob_integer, Adunare
run ob_integer, Inmultire
lea di, numar2
run ob_complex, Adunare
run ob_complex, Inmultire

sfarsit:
mov ax,4c00h
int 21h
end start
612

Instanţierea claselor se face prin macrodefiniţia define. Valorile iniţiale ale


datelor membre se pot seta prin instrucţiunea set (trebuie precizat obiectul de care
aparţin datele membre). Înaintea apelului procedurilor de adunare şi înmulţire, se
încarcă în registrele SI şi DI adresele numerelor folosite pentru aceste operaţii.
Rezultatele acestor apeluri se regăsesc în noile valori ale datelor membre.
Pentru verificarea operaţiilor se procedează identic ca în cazul variantei
anterioare (se foloseşte opţiunea /zi pentru asamblare şi opţiunea /v pentru link-
editare după care se verifică valorile membrilor celor două obiecte prin execuţie
pas cu pas în turbo debugger).
La compilarea acestui program se va obţine următorul listing:

E:\TOOLS\TASM\WORKING>tasm intcomp2
Turbo Assembler Version 4.1 Copyright (c) 1988, 1996
Borland International

Assembling file: intcomp2.ASM


< Creating class Integer ... >
< define nr_int as public ... >
< define function Adunare as public ... >
< define function Inmultire as public ... >
< Creating class Complex ... >
< Current class is derived from Integer ... >
< define nr_complex as public ... >
< define function Adunare as public ... >
< define function Inmultire as public ... >
Error messages: None
Warning messages: None
Passes: 1
Remaining memory: 422k

Macrodefiniţiile realizează o serie de afişări prin care indică structura


claselor create şi eventualele erori.
Dacă se încearcă schimbarea definiţiei clasei Integer în următoarea (nr_int
devine private):
class Integer
private_
date nr_int, dw, 0 ;partea intreaga
public_
func Adunare
func Inmultire
Integer ends

macrodefiniţiile vor semnala o serie de erori datorită încercării de iniţializare a


părţii întregi prin instrucţiunile set (set ob_integer, nr_int, 4 şi set ob_complex,
nr_int, 7) de după instanţierea obiectelor:
613
E:\TOOLS\TASM\WORKING>tasm intcomp2
Turbo Assembler Version 4.1 Copyright (c) 1988, 1996
Borland International

Assembling file: intcomp2.ASM


< Creating class Integer ... >
< define nr_int as private ... >
< define function Adunare as public ... >
< define function Inmultire as public ... >
< Creating class Complex ... >
< Current class is derived from Integer ... >
< define nr_complex as public ... >
< define function Adunare as public ... >
< define function Inmultire as public ... >
< **Error** Member nr_int is not accesible ... >
< **Error** Member nr_int is not accesible ... >
**Error** intcomp2.ASM(96) ISPROT?2(8) User error
**Error** intcomp2.ASM(97) ISPROT?2(8) User error
Error messages: 2
Warning messages: None
Passes: 1
Remaining memory: 422k

Se observă că apar două erori, una pentru set-ul pe clasa Integer, iar alta
pentru cel pentru clasa derivată Complex. Dacă cele două instrucţiuni set se şterg,
procesul de compilare nu va mai semnala nici un fel de eroare, ceea ce
demonstrează faptul că accesarea datelor membre private din cadrul procedurilor de
adunare şi înmulţire sunt corecte datorită apartenenţei acestor rutine la cele două
clase.

18.3.1. Optimizarea codului generat de macrodefiniţii

Aşa cum se va arăta spre sfârşitul capitolului, codul generat de


macrodefiniţii în varianta actuală este ineficient. Spre exemplu, secvenţa de
macroinstrucţiuni:

set ob_integer, nr_int, 4


lea si, numar1
run ob_integer, Adunare
run ob_integer, Inmultire

va genera următoarele instrucţiuni:

mov currOffSet,offset ob_integer


push bx
mov bx,word ptr currOffset
mov [bx][deplasament].nr_int1@val@, 4
pop bx
614
lea si, numar1
mov currOffset,offset ob_integer
call Adunare1@func@
mov currOffset,offset ob_integer
call Inmultire1@func@

După cum se observă, pentru indicarea instanţei curente (cea asupra căreia
se realizează operaţiile) se foloseşte o dată de tip word, currOffset. Aceasta se
încarcă în mod repetat în registrul BX, deoarece între instrucţiunile prezentate pot
exista şi alte instrucţiuni care afectează acest registru. În consecinţă, codul rezultat
din expandarea macroinstrucţiunilor este departe de a fi optim.
O optimizare foarte uşor de făcut, este aceea de a sacrifica un registru (aşa
cum în cazurile anterioare s-a folosit registrul SI), care urmează să ia locul
variabilei currOffset, dar care nu mai poate fi utilizat decât cu precauţie de către
utilizator (trebuie salvat în stiva înainte de blocul de operaţii în care se foloseşte şi
restaurat înainte de folosirea uneia dintre macroinstrucţiuni). Pentru aceasta s-a ales
registrul BX, pentru că acesta oricum se foloseşte la adresarea bazată din cadrul
macrodefiniţiilor set şi get.

După modificare, macrodefiniţiile get şi set devin:

;setare valoare
set MACRO ob, camp, v
;verifica daca suntem intr-o functie membra (nu se ;mai
specifica obiectul)

IFB <v>
operStanga movToOb, %curId, curOffset, ob, camp
ELSE
mov bx,OFFSET ob
operStanga movToOb, %(ob&Id), ob, camp, v
ENDIF
IFE gasit ;nu s-a gasit
%OUT < **Error** camp is not a member of ob ... >
.ERR
ELSE
gasit = 0 ;pt. urmatoarele cautari se
;revine la 0
ENDIF
deplasament = 0
ENDM

;incarcare valoare
get MACRO unde, ob, camp
;verifica daca suntem intr-o functie membra (nu se ;mai
specifica obiectul)
IFB <camp>
615
operDreapta movFromOb, unde, %curId, curOffset,
ob
ELSE
mov bx,OFFSET ob
operDreapta movFromOb, unde, %(ob&Id), ob, camp
ENDIF

IFE gasit ;nu s-a gasit


%OUT < **Error** camp is not a member of ob ... >
.ERR
ELSE
gasit = 0 ;pt. urmatoarele cautari se
;revine la 0
ENDIF
deplasament = 0
ENDM

De asemenea, şi macroinstrucţiunile movToOb şi movFromOb folosite


indirect (prin intermediul lui operDreapta şi operStanga) suferă modificări,
nemaifiind necesară încărcarea în registrul bx a offset-ului:

movToOb MACRO id, camp, v


mov [BX][deplasament].&camp&id&@val@, v
endm

movFromOb MACRO unde, id, camp


mov unde, [BX][deplasament].&camp&id&@val@
endm

În mod asemănător se procedează şi pentru macrodefiniţiile folosite la


apelul funcţiilor. Odată făcute aceste modificări, codul generat pentru secvenţa de
instrucţiuni prezentată mai sus, se va micşora, producând o creştere semnificativă
de viteză de execuţie:

mov bx,offset ob_integer


mov [bx][deplasament].nr_int1@val@, 4
lea si, numar1
mov bx,offset ob_integer
call Adunare1@func@
mov bx,offset ob_integer
call Inmultire1@func@
616

Optimizarea s-a produs pentru funcţia set prin preluarea directă a


deplasamentului în registrul BX. O optimizare ce mai poate fi adusă codului, dar
care ducă la o scădere a transparenţei implementării obiectelor, este încărcarea
explicită în registrul BX a deplasamentului instanţei curente. Astfel, nu ar mai fi
necesară încărcarea repetată a deplasamentului după cum se observă în codul
următor:

mov bx,offset ob_integer


mov [bx][deplasament].nr_int1@val@, 4
lea si, numar1
call Adunare1@func@
call Inmultire@funct@

Făcându-se aceste simple modificări codul rezultat în urma expandării va fi


asemănător cu cel oferit de prima variantă, cea care foloseşte structuri simple, dar
va oferi în plus încapsulare, moştenire şi polimorfism, toate realizate în faza de
asamblare. În acest mod, viteza de execuţie a programelor scrise în cele două
variante va fi identică, dar avantajele oferite de cea de-a doua implementare vor fi
net superioare.

18.4. Folosirea specificaţiilor proprii limbajului de asamblare

Începând cu versiunea 3.0, asamblorul produs de firma Borland conţine


elementele fundamentale ce permit programarea orientată obiect în limbaj de
asamblare, şi anume structurile de tip clasă. În Turbo Assembler, datele şi codul se
pot încapsula în declaraţii speciale de tip STRUC, numite clase. O clasă (numită
clasă derivată) poate moşteni codul şi datele unei alte clase (numită clasă de bază).
Subrutinele clasei, sau metodele, pot fi statice (apelate direct) sau virtuale (apelate
prin căutarea adresei subrutinei într-o tabelă a metodelor virtuale).
Sintaxa unei declaraţii de clasă este:

<nume> STRUC <modificator> <nume_clasa> METHOD {


declaraţii metode
}
declaraţii date membre
ENDS <nume_clasa>

unde modificator poate fi NEAR sau FAR, însoţit sau nu de directiva GLOBAL.
Declaraţia metodelor clasei are următoarea sintaxă:

[virtual] nume_metoda : tip = nume_eticheta


617

Numele metodei nu este supus restricţiei de unicitate, ca în cazul declaraţiilor de


membri ai tipului de date STRUC. Numele etichetei prin care este implementată
metoda trebuie să fie unic. Tipul poate fi WORD sau DWORD.
Un exemplu de implementare a unei clase în limbaj de asamblare orientat
obiect:

GLOBAL Baza_constructor: PROC


GLOBAL Baza_actiune: PROC

Baza STRUC GLOBAL METHOD{


constructor:WORD = Baza_constructor
VIRTUAL actiune:WORD = Baza_actiune
}
x DD 0.0
ENDS Baza

Următoarele declaraţii definesc două obiecte ale clasei Baza, cu iniţializare


sau fără:

DATASEG
b1 Baza <1.1>
b2 Baza < >

Prin folosirea moştenirii se creează clase noi din cele deja existente. Clasa
derivată este o copie a clasei de bază la care se pot adăuga noi metode sau
variabile. Metodele adăugate pot fi complet noi sau le pot înlocui pe cele cu acelaşi
nume în clasa de bază. În plus, metodele din clasa nouă pot apela metodele din
clasa de bază pe care le înlocuiesc. Nu se pot înlocui date ale clasei de bază.
În continuare se prezintă un exemplu de implementare a unei clase derivate
din cea anterioară, în limbaj de asamblare orientat obiect:
GLOBAL Deriv_constructor: PROC
GLOBAL Deriv_actiune: PROC

Deriv STRUC GLOBAL Baza MEHOD{


constructor:WORD = Deriv_constructor
VIRTUAL actiune:WORD = Deriv_actiune
}
TBLPTR
y DD 0.0
ENDS Deriv

Metodele virtuale diferă de metodele statice prin modul în care sunt


adresate. În loc să calculeze adresa unei metode virtuale la asamblare, asamblorul
generează instrucţiuni care extrag adresa la momentul execuţiei din VMT.
Apelurile metodelor virtuale sunt indirecte, fiind făcute prin referinţă la intrările
dintr-o VMT.
618

Inserarea uneia sau mai multor metode virtuale într-o declarare a unei clase
se realizează prin prefaţarea declarării metodei cu cuvântul cheie VIRTUAL. Apoi,
în secţiunea de date a clasei se inserează un pointer la VMT folosind directiva
TBLPTR.
Cuvântul cheie TBLINST declară o instanţă a tabelei metodelor virtuale
(VMT), definind spaţiul în memorie pentru aceasta. Simpla declarare a unei
instanţe VMT nu realizează şi iniţializarea pointerului la VMT pentru diversele
obiecte ale respectivei clase.

DATASEG
TBLINST

La următorul pas, pentru fiecare obiect în parte se include în constructorul


său instrucţiunea TBLINIT <adresa>, unde adresa poate fi un registru sau o locaţie
de memorie.
PROC Deriv_constructor
TBLINIT Deriv PTR si
ret
ENDP Deriv_constructor

Pointerul la metoda statică constructor este iniţializat cu adresa subrutinei


asociate, şi anume Deriv_constructor. Constructorul clasei trebuie apelat pentru
toate obiectele clasei. Fiecare astfel de obiect are pointerul propriu la VMT, care
trebuie iniţializat individual. Apelarea constructorului se realizează ca şi pentru
oricare altă metoda statică. De exemplu, în segmentul de date, se defineşte mai
întâi un obiect:

DATASEG
d1 Deriv < >

În segmentul de cod, se adresează d1 prin intermediul ds:si şi se apelează


constructorul clasei:

CODESEG
mov si, offset d1
CALL si METHOD Deriv:constructor

Apelarea metodei virtuale actiune din clasa Baza se exemplifică în


continuare:

CODESEG
mov si, offfset b1
CALL Baza PTR si METHOD Baza:actiune
619

Dacă SI adresează un obiect al unei clase derivate, atunci subrutina actiune


a clasei derivate este apelată. Astfel, în secvenţa de mai jos, deşi instrucţiunea
CALL...METHOD specifică Baza, instrucţiunea de fapt apelează metoda actiune a
clasei derivate.

CODESEG
mov si, offset d1
CALL Baza PTR si METHOD Baza:actiune

Aceasta este o dovadă a polimorfismului, proces prin care se creează clase


care folosesc metode virtuale pentru a selecta acţiuni la momentul execuţiei. Deci
obiectul însuşi determină care metodă virtuală să fie apelată.
În anumite cazuri, apelul de forma CALL...METHOD se poate înlocui cu
JMP...METHOD pentru a optimiza metodele care se termină cu apeluri către alt
metode.
Pentru a apela o funcţie virtuală a clasei de bază din interiorul unei funcţii
virtuale a clasei derivate se foloseşte un apel de funcţie statică. De exemplu, în
metoda virtuală actiune din clasa derivată Deriv se apelează metoda virtuală
actiune din clasa de bază Baza:

PROC Deriv_actiune
call Baza_actiune
ret
ENDP Deriv_actiune

În continuare se reia exemplul numerelor întregi şi complexe pentru


exemplificarea acestei variante.
Mai întâi este necesară folosirea unor directive:

IDEAL
JUMPS
LOCALS @@
MODEL large, PASCAL
STACK 1000h
. . . . .

Directiva IDEAL selectează modul de lucru Ideal al turbo asamblorului.


Acest mod face ca membrii structurilor să fie locali acestora, ceea ce ne permite să
avem nume de date membre identice în structuri diferite. Acest mod impune
folosirea directivei GLOBAL pentru metode.
Cea de-a doua directivă, JUMPS, permite salturi condiţionale automate,
făcând posibilă generarea unui cod mai eficient din partea asamblorului. Directiva
LOCALS @@ previne o serie de conflicte rezultate din folosirea variabilelor cu
acelaşi nume la nivel global şi la nivel de procedură. Prin MODEL large, PASCAL
se declară modelul de memorie ca fiind large şi folosirea modului de transmitere al
620

parametrilor ca în Pascal (parametrii sunt transmişi în ordinea în care sunt


declaraţi), mai potrivit programării orientate obiect în limbajul de asamblare. STACK
1000h declară mărimea stivei.
Urmează codul în care se declară şablonul oferit de cele două clase şi
procedurile din cadrul acestora:

...
GLOBAL Int_Adunare: PROC
GLOBAL Int_Inmultire: PROC
GLOBAL Complex_Adunare: PROC
GLOBAL Complex_Inmultire: PROC

STRUC Integer METHOD{


Adunare:WORD = Int_Adunare
Inmultire:WORD = Int_Inmultire
}
nr_int DW 0
ENDS Integer

CODESEG

PROC Int_Adunare
ARG @@int:word
USES ax

mov ax, [@@int]


add ax, [(Integer PTR si).nr_int]
mov [(Integer PTR si).nr_int], ax
ret
ENDP Int_Adunare

PROC Int_Inmultire
ARG @@int:word
USES ax

mov ax, [@@int]


mul [(Integer PTR si).nr_int]
mov [(Integer PTR si).nr_int], ax
ret
ENDP Int_Inmultire

STRUC Complex Integer METHOD{


Adunare:WORD = Complex_Adunare
Inmultire:WORD = Complex_Inmultire
}
nr_complex DW 0
ENDS Complex

PROC Complex_Adunare
621
ARG @@int:word, @@compl:word
USES ax

CALL si METHOD Integer:Adunare, [@@int]


mov ax, [@@compl]
add ax, [(Complex PTR si).nr_complex]
mov [(Complex PTR si).nr_complex], ax
ret
ENDP Complex_Adunare

PROC Complex_Inmultire
ARG @@int:word, @@compl:word
USES ax, bx

mov ax, [@@compl]


mul [(Complex PTR si).nr_complex]
mov bx, ax
mov ax, [@@int]
mul [(Complex PTR si).nr_int]
sub ax, bx
push ax
mov ax, [@@compl]
mul [(Complex PTR si).nr_int]
mov bx, ax
mov ax, [@@int]
mul [(Complex PTR si).nr_complex]
add ax, bx
mov [(Complex PTR si).nr_complex], ax
pop ax
mov [(Complex PTR si).nr_int], ax
ret
ENDP Complex_Inmultire
...

Fiecare metodă a unei clase trebuie să lucreze cu o anumită instanţă a clasei


respective. Această instanţă poate fi transmisă metodei prin oricare din modurile de
transmitere a parametrilor, dar prin convenţie se transmite adresa acesteia prin
perechea de registre DS:SI. De aceea accesul la o dată membră în cadrul unei
metode a clasei se face prin (<nume_clasa> PTR si).<nume_camp>.
Parametrii sunt transmişi prin stivă, iar extragerea acestora se face prin
directiva ARG, urmată de numele dat parametrului şi tipul acestuia. Folosirea
prefixului @@ în numele parametrilor face ca aceştia să devină locali procedurii şi
să nu fie confundaţi cu parametrii globali cu acelaşi nume. Se face uz de directiva
USES prin care se indică regiştrii ce vor fi folosiţi, urmând ca asamblorul să includă
push-urile şi pop-urile necesare pentru revenirea la valorile iniţiale ale acestor
regiştri.
Pentru folosirea acestor clase se face apel la datele şi operaţiile de test
prezentate şi la celelalte două variante:
622

...
DATASEG
ob_integer Integer <4>
ob_complex Complex <7, 5>

CODESEG
start:
mov ax,@data
mov ds,ax

mov si, offset ob_integer


CALL si METHOD Integer:Adunare, 10
CALL si METHOD Integer:Inmultire, 10
mov si, offset ob_complex
CALL si METHOD Complex:Adunare, 10,5
CALL si METHOD Complex:Inmultire, 10,5

sfarsit:
mov ax,4c00h
int 21h
end start

Înaintea apelurilor procedurilor se încarcă în registrul SI deplasamentul


instanţei. Parametrii au fost transmişi prin specificarea unor constante după numele
procedurii (numele este cel din cadrul clasei şi nu cel efectiv) în cadrul
instrucţiunilor CALL. În locul acestor constante se puteau specifica şi identificatori
de regiştri sau zone de memorie.

18.5. Analiza comparativă a variantelor de implementare a obiectelor

Fiecare dintre cele trei variante de implementare a obiectelor în limbajul de


asamblare are atât avantaje cât şi dezavantaje. Prima variantă, cea prin care se
foloseau structuri simple, este uşor de folosit şi nu necesită nici un fel de construcţii
speciale. Prezintă însă o serie de deficiente: nu asigură o protecţie a datelor şi
metodelor, în cazul moştenirii multiple apare problema deplasamentelor datelor
membre ale claselor de bază, indicarea obiectelor se face explicit prin încărcarea
într-un registru a adreselor, lipsesc elementele avansate de programare orientată
obiect, cum este polimorfismul, clasele prietene şi funcţiile virtuale.
Cea de-a doua variantă, se remarcă printr-o transparenţă mare a
mecanismelor de implementare a obiectelor, ceea ce poate uşura substanţial munca
programatorului, precum şi prin optimalitatea codului datorită folosirii
macrodefiniţiilor (durează mai mult compilarea, dar codul executabil rezultat este
optim). Sintaxa a fost construită să semene cât mai mult cu cea folosită de limbajul
C++, cu care majoritatea programatorilor sunt familiarizaţi. Varianta prezentată nu
suportă moştenirea pe mai multe niveluri (dar care nu este imposibil de realizat
623

datorită existenţei construcţiilor macro recursive şi repetitive) şi nici funcţiile


virtuale, care necesită cod efectiv (nu numai macroinstrucţiuni) folosit în timpul
executării programului. De asemenea, este necesară scrierea tuturor acestor
macroinstrucţiuni şi includerea lor în fişierele sursă.
Ultima variantă, care foloseşte construcţii speciale orientate obiect specifice
implementării propuse de firma Borland, acoperă multe din aspectele programării
orientate obiect. În plus faţă de celelalte două variante, oferă folosirea funcţiilor
virtuale. Trebuie ţinut cont că această implementare este specifică doar produsului
realizat de firma Borland şi, deci, nu reprezintă un standard. Asambloarele oferite
de alte firme nu cuprind aceste construcţii, iar dacă se doreşte un cod sursă
compatibil (cu atât mai mult cu cât chiar sintaxa oferită de Borland poate suferi
schimbări) trebuie făcut apel la alte variante. De asemenea, la fel ca în prima
variantă, indicarea instanţelor nu se face la fel de uşor ca în cea de-a doua metodă,
ci prin încărcarea explicită a adreselor în regiştri. Sintaxa nu permite o verificare
completă a codului chiar înainte de link-editare, ca în cazul variantei folosirii
macrodefiniţiilor, erorile fiind uneori greu de depistat, iar efectul acestora apare
doar în timpul execuţiei.
Pentru o comparaţie mai profundă a celor trei variante de implementare,
trebuie calculaţi şi o serie de indicatori, cum ar fi: numărul de instrucţiuni scrise,
numărul efectiv de instrucţiuni folosite de asamblor, timpul de execuţie pentru o
problemă dată etc.
Pentru a afla valorile unor astfel de indicatori în fiecare din variante trebuie
apelat la un alt exemplu decât cel prezentat anterior. Astfel, pentru aflarea duratei
de execuţie trebuie construit un exemplu de calcul care necesită un timp de
execuţie mai lung, şi astfel cuantificabil. Se va lucra tot pe cele două clase
prezentate în paginile de mai sus, dar pentru testarea operaţiilor acestora se va opta
pentru un cod ciclic. Ca număr de iteraţii s-a optat pentru 10.000.000, ceea ce face
ca timpul de execuţie să fie de ordinul zecilor de secunde. În cadrul unei iteraţii se
realizează înmulţiri pentru ambele clase, utilizându-se datele de test deja
prezentate.
Pentru prima variantă, cea care foloseşte doar structuri simple, noul cod va
fi următorul:

; vechea porţiune de cod prin care se declara clasele


; si operaţiile acestora ...
...
contor1 dw 2000
contor2 dw 5000 ; 2000 x 5000 =
10.000.000 de iteraţii
.code
start:
mov ax,@data
mov ds,ax
624
lea si, numar1 ;[si] = numar1, [si+2] = numar2

mov cx, contor1


next1:
push cx
mov cx, contor2
next2:
mov ob_integer.nr_int, 4
mov ob_complex.nr_int, 7
mov ob_complex.nr_complex, 5

lea di, ob_integer


call ob_integer.inm_int
lea di, ob_complex
call ob_complex.inm_compl
loop next2
pop cx
loop next1

sfarsit:
mov ax,4c00h
int 21h
end start

Pentru varianta în care se folosesc macroinstrucţiuni, noul cod va fi:


; vechea porţiune de cod prin care se declara clasele
; si operaţiile acestora ...
...
contor1 dw 2000
contor2 dw 5000 ; 2000 x 5000 = 10.000.000 de
iteraţii
.code
start:
mov ax,@data
mov ds,ax

lea si, numar1


lea di, numar2
mov cx, contor1
next1:
push cx
mov cx, contor2
next2:
set ob_integer, nr_int, 4
set ob_complex, nr_int, 7
set ob_complex, nr_complex, 5
run ob_integer, Inmultire
run ob_complex, Inmultire
loop next2
pop cx
625
loop next1

sfarsit:
mov ax,4c00h
int 21h
end start

Iar pentru ultima variantă, în care se foloseşte implementarea Borland,


codul folosit pentru testare va fi:
; vechea porţiune de cod prin care se declara clasele
; si operaţiile acestora ...
...
contor1 dw 2000
contor2 dw 5000 ; 2000 x 5000 = 10.000.000 ;de
iteraţii
CODESEG
start:
mov ax,@data
mov ds,ax

mov cx, contor1


next1:
push cx
mov cx, contor2
next2:
mov si, offset ob_integer
mov [(Integer PTR si).nr_int], 4
CALL si METHOD Integer:Inmultire, [numar1]
mov si, offset ob_complex
mov [(Complex PTR si).nr_int], 7
mov [(Complex PTR si).nr_complex], 5
CALL si METHOD Complex:Inmultire, [numar1], [numar2]
loop next2

pop cx
loop next1
sfarsit:
mov ax,4c00h
int 21h
end start

Folosindu-se aceste secvenţe de cod se obţin indicatori atât cantitativi, cât şi


calitativi referitori la cele trei variante implementate, tabelul 18.5.
626

Tabelul 18.1. Analiza comparativă a celor trei variante


Varianta Varianta ce foloseşte
ce macroinstrucţiuni Implementarea
Indicator foloseşte Variant Borland orientată
structuri Varianta obiect
a
simple optimizată
iniţială
Linii de cod
95 93 93 100
scrise
Linii de cod
asamblate (după 95 147 96 121
expandare)
Linii de cod
folosite în 73 124 73 81
timpul execuţiei
Timpul de
execuţie 20 37 22 27
(secunde)
Încapsulare
(protecţia  
membrilor)
Moştenire   
Polimorfism   

Funcţii virtuale 

18.5.1. Analiza indicatorilor rezultaţi

Tabelul prezintă patru indicatori, trei referindu-se la numărul liniilor de cod


(ceea ce în limbaj de asamblare este echivalent cu numărul instrucţiunilor), iar
ultimul este timpul de execuţie exprimat în secunde. Primul indicator se referă la
numărul de linii scrise de programator (neincluzând codul sursă al
macrodefiniţiilor), în timp ce al doilea reflectă numărul de instrucţiuni după
efectuarea expandărilor macrodefiniţiilor. Al treilea indicator cuprinde doar
instrucţiunile ce intervin în mod activ în timpul execuţiei (se elimină unele
instrucţiuni cum ar fi etichetele, directivele de compilare etc.). Analiza se poate
dezvolta apelându-se la lungimile în octeţi a instrucţiunilor. După aceşti patru
indicatori se indică existenţa sau inexistenţa a patru concepte fundamentale ale
programării orientate obiecte: încapsulare, moştenire, polimorfism şi funcţii
virtuale.
627

Prima variantă de implementare prezintă cele mai puţine linii de cod şi cel
mai mic timp de execuţie. Acest lucru este explicat prin lipsa celor mai elementare
concepte ale programării orientate obiect (implementarea nu oferă nici măcar o
protecţie a datelor sau polimorfism). Tot ceea ce scrie programatorul se reflectă în
mod identic în codul sursă supus asamblării, singura abatere de la programarea
tradiţională în cod de asamblare o constituie includerea pointerilor către proceduri
în cadrul structurilor.
Cea de-a doua metodă de implementare este prezentată în cele două
variante, observându-se câştigul substanţial de viteză al formei optimizate. Aceasta
se apropie ca performanţă de metoda care foloseşte structuri simple (22 de secunde
de execuţie faţă de 20), oferind în plus programatorului încapsulare, polimorfism şi
moştenire multiplă. Prima variantă este mult mai lentă datorită folosirii unei date
suplimentare pentru deplasare în cadrul structurilor şi a salvărilor repetate în stivă a
registrului BX. Dar, aceasta oferă un grad mai ridicat de încapsulare. Utilizatorul
macroinstrucţiunilor nu este supus nici unor restricţii în folosirea registrului BX,
care în cazul variantei optimizate trebuie salvat şi restaurat de fiecare dată ţinând
cont că şi codul expandat de macrodefiniţii foloseşte acest registru. De asemenea,
varianta a doua, din motive de optimizare a codului, necesită încărcarea explicită în
registrul BX a instanţei asupra căreia se lucrează, ceea ce duce la creşterea
probabilităţilor de apariţie a erorilor.
Ultima variantă de implementare, cea oferită de firma Borland, oferă
performanţe medii şi acest lucru se explică prin modul în care sunt trataţi
parametrii procedurilor. Aceştia sunt transmişi prin stivă, iar pentru folosirea lor se
rezervă regiştri care trebuiesc salvaţi înainte de încărcare şi restauraţi la ieşirea din
procedură. Pentru a urmări codul generat, să luăm procedura de adunare a
numerelor complexe:

PROC Complex_Adunare
ARG @@int:word, @@compl:word
USES ax

CALL si METHOD Integer:Adunare, [@@int]


mov ax, [@@compl]
add ax, [(Complex PTR si).nr_complex]
mov [(Complex PTR si).nr_complex], ax
ret
ENDP Complex_Adunare

Macrodefiniţiile ARG şi USES introduc următoarele modificări codului de


mai sus:
PROC Complex_Adunare
PUSH BP
MOV BP,SP
PUSH AX
628
CALL si METHOD Integer:Adunare, [@@int]
mov ax, [@@compl]
add ax, [(Complex PTR si).nr_complex]
mov [(Complex PTR si).nr_complex], ax
POP AX
POP BP
ret
ENDP Complex_Adunare

Pentru accesul la variabilele din stivă, trebuie salvată vechea valoare a


registrului BP, care va fi folosit pentru accesarea stivei ( mov BP, AX). Urmează
salvarea registrului AX folosit pentru preluarea uneia dintre variabile. Înaintea
revenirii din procedură se restaurează vechile valori ale regiştrilor AX şi BP. Pentru
reutilizarea codului, este apelată metoda de adunare din clasa de bază, care, la
rândul ei, va relua secvenţa de salvări şi restaurări de regiştri. Toate aceste operaţii
duc la o încetinire semnificativă în execuţie atunci când apelurile de proceduri sunt
folosite frecvent. Regiştrii, cum sunt AX şi BX, sunt salvaţi în mod repetat cu toate
că păstrarea vechilor valori nu prezintă importanţă. Din acest punct de vedere,
această metodă se aseamănă cu prima variantă a implementării prin
macroinstrucţiuni, oferind un grad mare de transparenţă şi securitate, dar şi o
încetinire a calculelor.
Alegerea uneia dintre variante trebuie făcută în strânsă corelaţie cu
problema de rezolvat:
 dacă se dezvoltă cod relativ mic, care se doreşte a fi rapid se optează
pentru prima variantă;
 dacă sunt necesare facilităţile de moştenire, încapsulare sau polimorfism
pentru probleme mai complexe, dar pentru care timpul de execuţie este
critic, se va opta pentru varianta optimizată ce foloseşte
macroinstrucţiuni;
 dacă codul rezultat trebuie să fie sigur, punându-se accentul pe
securitatea oferită în timpul execuţiei şi mai puţin pe timpul de execuţie,
implementarea Borland orientată obiect sau prima variantă ce foloseşte
macrodefiniţii se dovedesc alegerile viabile;
 dacă se doresc secvenţe de program complexe, care apelează la lucrul cu
pointeri spre obiecte ale căror tipuri sunt cunoscute abia în timpul
execuţiei, cea mai buna alegere este implementarea Borland care oferă
suport pentru funcţii virtuale.
Fiecare variantă este optimă pentru anumite tipuri de probleme şi cerinţe ale
acestora, programatorului revenindu-i responsabilitatea alegerii. Prima variantă este
simplă şi nu necesită nici un fel de efort suplimentar, dar lipsurile acesteia sunt
evidente când se dezvoltă programe complexe. Variantele ce folosesc
macrodefiniţii necesită scrierea sau procurarea acestora, iar implementarea oferită
de firma Borland este recunoscută doar de asamblorul acesteia.
629

19
STRUCTURI DE PROGRAME
O structură de program se memorează în segmente de date (x), segmente de
stivă (y) şi segmente de cod (z) şi este definită prin tripletul (nx, ny ,nz), unde:
 nx – numărul de segmente de date;
 ny – numărul de segmente de stivă;
 nz – numărul de segmente de cod.
În continuare se prezintă tipurile principale de structuri de program atât prin
plasarea în memorie (segmente) cât şi în raport cu raporturile dintre proceduri.

19.1 Programul ca singură secvenţă (0, 0, 1)

Această structură de program de tip monolit este caracteristică problemelor


simple, cărora li se dă soluţie de către un singur programator. Operanzii (variabilele
simple, masivele, tipurile derivate) se definesc în acelaşi segment în care se află
secvenţa de instrucţiuni executabile.
Programul ca singură secvenţă corespunde şi construcţiilor elaborate de
către programatorii începători care nu au capacitatea de a structura la nivel de
proceduri sau secvenţe apelabile, operaţii complexe cu caracter repetitiv. Tot atât
de bine, acest mod de a soluţiona o problemă este specifică şi programatorilor care
urmăresc minimizarea salturilor necondiţionate, generate de apelurile de proceduri
(instrucţiunea call) şi de revenirile la procedura apelatoare (instrucţiunea ret).
De exemplu, evaluarea funcţiei:

x>0
x+y+z+w y>0
z>0
w>0

F(x,z,y,w)= x<0
x2+y2+z2+w2 y<0
z<0
630

w<0

|x|+|y|+|z|+|w| în rest

se efectuează cu un program în care se definesc proceduri pentru :

ADD3 – însumarea a trei elemente;


PDW2 – ridicarea la putere a unui număr;
MODUL – calculul modulului unui număr.

Textele sursă asociate procedurii pentru adunarea a 4 numere este:

ADD3 PROC ; s=a+b+c+d


push bp
mov bp, sp
push ax
mov ax, [bp+4] a ;ax:=0
add ax, [bp+6] b ;ax:=ax+b
add ax, [bp+8] c ;ax:=ax+c
add ax, [bp+10] d ;ax:=ax+d
mov [bp+12], ax ;s:=ax
pop ax
pop bp
ret
endp

Încărcarea pe stivă a adreselor parametrilor reali înainte de efectuarea


apelului procedurii ADD3 se realizează:

mov ax, offset s ;variabila unde se stochează suma


push ax
mov ax, offset d;
push ax
mov ax, offset c
push ax
mov ax, offset b
push ax
mov ax, offset a
push ax
call ADD3

Textul sursă pentru ridicarea la pătrat a unui număr este:

POW2 proc
push bp
mov bp, sp
push ax
631
push dx
mov ax, [bp+4]
mul [bp+4]
mov [bp+6], ax
pop dx
pop ax
pop bp
ret
endp

Pentru evaluarea expresiei e=a*a, încărcarea pe stivă a adresei parametrilor


este realizată astfel:

mov ax, offset a


push ax
mov ax, offset e
push ax
call POW2

Textul sursă pentru procedura de calcul a modulului unui număr s=|a| este:

modul proc
push bp
mov bp,sp
push ax
mov ax, [bp+4]
neg ax
mov [bp+6], ax
pop ax
pop bp
ret
endp

Textul sursă al programului cu apelarea celor trei proceduri este:

start:
mov ax, @data
mov ds, ax
cmp x, 0
jle alfa
cmp y, 0
jle alfa
cmp z, 0
jle alfa
mov ax offset fxyz
push ax
mov ax, offset w
push ax
mov ax, offset z
push ax
632
mov ax, offset y
push ax
mov ax, offset x
push ax
jmp suma
alfa:
cmp x, 0
jz beta
cmp y, 0
jz beta
cmp z, 0
jz beta
cmp w, 0
jz beta
mov ax, offset xp
push ax
mov ax, offset x
push ax
call POW2
mov ax, offset yp
push ax
mov ax, offset y
push ax
call POW2
mov ax, offset zp
push ax
call POW2
mov ax, offset wp
push ax
mov ax, offset w
push ax
call POW2
mov ax, offset s
push ax
mov ax, offset wp
push ax
mov ax, offset zp
push ax
mov ax, offset yp
push ax
mov ax, offset xp
push ax
jmp suma
beta:
mov ax, offset mx
push ax
mov ax, offset x
push ax
call modul
mov ax, offset my
push ax
mov ax, offset y
633
push ax
call modul
mov ax, offset mz
push ax
mov ax, offset z
push ax
call modul
mov ax, offset mw
push ax
mov ax, offset w
push ax
call modul
mov ax, offset s
push ax
mov ax, offset w
push ax
mov ax, offset mz
push ax
mov ax, offset my
push ax
mov ax, offset mx
push ax

suma:
call ADD3
...
;procedura pentru conversia de la binar la şir de caractere
;şi reprezentare f(x, y, z, w)
...
mov 4c00h
int 21h
end start

x dw 2
y dw 3
z dw 4
w dw 1
px dw ?
py dw ?
pz dw ?
pw dw ?
mx dw ?
my dw ?
mz dw ?
mw dw ?
s dw ?

Structura grafică a programului este dată în figura 19.1.


634

start
x>0

y>0

z>0

w>0
x=0
push …
y=0
push …
z=0
push …
w=0
|x|
push …
x2 |y|

y2 |z|

z2 |w|
s=a+b+c+d
w2 push …

conversiepush … push …

push …
afisare push …

push …
push …
stop
push …
635

Figura 19.1 – Graficul asociat programului PP pentru


evaluarea funcţiei într-un punct folosind proceduri

Pentru eliminarea salturilor necondiţionate impuse de apelurile procedurilor


se procedează la conturarea unui program numit PM ca un tot unitar, în care
secvenţele alcătuiesc o singură entitate, având textul sursă:

...
cmp x, 0
jle alfa
cmp y, 0
jle alfa
cmp z, 0
jle alfa
cmp w, 0
jle alfa
add ax, z
add ax,y
add ax,x
mov s, ax
jmp imprimare
alfa: cmp x, 0
jz beta
cmp y, 0
jz beta
cmp z, 0
jz beta
cmp w, 0
jz beta
mov ax, x
mov mx, ax
neg mx
mov ax, y
mov my, ax
neg my
mov ax, z
mov mz, ax
neg z
mov ax, w
mov mw, ax
neg w
mov ax, mx
add ax, my
add ax, mz
add ax, mw
mov s, ax
jmp imprimare
beta: xar bx, bx
636
mov ax, x
mul x
add bx, ax
mov ax, z
mul y
add bx, ax
mov ax, z
mul z
add bx, ax
mov ax, w
mul w
add bx, ax
mov s, ax
...

Dezavantajul programului PM este dat de faptul că se construieşte de către


un singur programator. Programul în care se definesc proceduri se elaborează în
echipe de programatori, fiecărui programator revenindu-i sarcina de a elabora
simultan un număr proceduri şi de a obţine reducerea duratei de elaborare a
programului.

19.2 Programul din secvenţe distincte grupate într-un segment (1, 1, 1)

Acest tip de construcţie este compusă dintr-un segment de date, un segment


stivă şi un segment de cod.
Segmentul de cod conţine secvenţe referite instrucţiuni de apel
(instrucţiunea call). Evaluarea expresiei:

e=(a+min{xi}+max{xi})*(b+min{yi})-(c+max{wi})-(min{zi}+max{zi})

se realizează prin programul PEVAL al cărui text sursă este:

mov si, offset x


mov cx, n
mov bx, a
minim:
mov ax, [si]
ciclul1:
cmp ax, [si+2] minim
jle salt_1
mov ax, [si+2]
salt1:
inc si
inc si
loop ciclul1
ret
add bx, ax ; bx:=bx+min{xi}
mov cx, n
637
mov si, offset y
maxim:
mov ax, [si]
ciclul2:
cmp ax, [si+2]
jge salt2
mov ax, [si+2] max
salt2;
inc si
inc si
loop ciclul2
ret
add bx, ax
mov e, bx
mov bx, b
mov si, offset y
mov cx, m
call minim
add bx, ax
mov si, offset y
mov cx, m
call maxim
add bx, ax
mov ax, e
mul bx
mov e, ax
mov ex2, dx
mov bx, c
mov si, offset w
mov cx, m
call maxim
add bx, ax
sub e, bx
sbc ex2, 0 e:=e-(c+max{xi})
mov si offset z
mov cx, k
call minim
mov bx, ax
mov si, offset z
mov cx, k
call maxim
add bx, ax
shl bx, 1
sub e, bx
sbc ex2, 0

Programului PEVAL i se asociază secvenţele:

Sx – iniţializări pentru operaţii pe masivul x,


Smin – găsire element minim;
638

Smax – găsire element maxim;


Sy – iniţializări pentru operaţii pe masivul y;
Se – evaluări expresie Se;
Sw – iniţializări pentru operaţii cu masivul w;
Sz – iniţializări pentru operaţii pe masivul z.

Secvenţele se activează astfel:

Sx Smin Sx Smax Se Sy Smin Sy Smax Se Sw Smin Se Sz Smin Sz


Smax Se.

Acest mod de structurare a secvenţelor este asemănător construirii


programelor în limbajul BASIC, cu utilizarea instrucţiunilor GOSUB.
De asemenea, programatorii în limbajul COBOL
structurează programele în paragrafe, delimitate prin etichete,
paragrafe pe care le referă cu instrucţiunea PERFORM.
Paragrafele se scriu în interiorul programului sau se poziţionează după
instrucţiunea STOP RUN. De exemplu, programul TAB pentru afişarea unui cap
de tabel având structura:

Denumire Cant Pret unitar Valoare


(30) (4) (5) (8)
0 1 2 3
Prod1 10 2 20
Prod2 5 3 150
Prod3 2 13 26
Total 196

are textul sursă:

rând1 struc
filler1 db 10
linii db 60
ends
rând2 struc
filler2 db 10 dup (‘’)
col21 db ‘I ‘
denumire 2 db 30 dup (‘’)
col22 db ‘ , ‘
cant2 db 4 dup (‘’)
col23 db ‘ , ‘
pret2 db 5 dup (‘ ‘)
col24 db ‘ , ‘
639
valoare2 db 8 dup (‘ ‘)
col25 db ‘ ,‘
rând3 struc
filler db 10 dup (‘ ‘)
col31 db ‘, ‘
denumire3 db ’Denumire/30’
col32 db ‘ , ‘
cant3 db ‘CANT’
col33 db ‘ , ‘
pretunit3 db ‘ PU’
col34 db ‘ , ‘
valoare3 db ‘ valoare’
col35 db ‘ ,’

Pentru afişarea capului de tabel se foloseşte secvenţa:

call scrie-rand1
call scrie-rand2
call scrie_rand2
call scrie_rand3
call scrie_rand2
call scrie_rand2
call scrie_rand1
call scrie_rand4 (I0I1I2I3)
call scrie_rand1

Pentru afişarea datelor tabelului se foloseşte secvenţa:

mov cx, n (număr produse)


mov si offset vect_prod
ciclu:
………….
…………. ;calcul valoare, total
…………. ;conversii
…………. ;call scrie_rând5
………….
loop ciclu
………….
…………. ;conversie total
…………. ;mută în rând 6 la coloana
…………. ;de total şirul convertit
…………. ;call scrie_rând6
………….
mov ax, 4c00h
int 21h
serie_rând1:
………….
………….
ret
serie_rând2:
640
………….
………….
ret
serie_rând6:
………….
………….
ret

Secvenţele care se referă prin instrucţiunea call sunt dispuse după


instrucţiunile care definesc transferul controlului programului către sistemul de
operare:

mov ax, 4c00h


ret 21h

19.3 Proceduri incluse în segmentul programului principal (1, 1, 1)

Aplicaţia conţine un segment de date, un segment pentru stivă şi un


segment pentru cod.
Corespunde situaţiei în care conducătorul echipei de programatori
gestionează cu rigurozitate proiectul. El defineşte datele cu care se lucrează,
structurează aplicaţia pe module (proceduri sau macrodefiniţii) pentru a fi elaborate
simultan de către membrii echipei.
Dacă A, B sunt matrice pătrate şi C, D, G sunt vectori, evaluarea expresiei:

F=((A2+B’)*C+D)*G

presupune scrierea de proceduri:


 construirea unei matrice transpuse;
 efectuarea produsului a două matrice;
 calcul produs matrice vector coloană;
 adunarea a doi vectori;
 calcul produs scalar a doi vectori.
Şeful echipei de programatori construieşte segmentul de date:

Datele seg ‘DATE’


NCOL EQU 4
NLIN EQU 4
A dw 7, 5, 8, 13
dw 3, 1, 8, 6
dw 1, 0, 3, 7
dw 4, 5, 11, 2
B dw 2, 4, 9, 1
dw 32, 2, 5, 7
dw 4, 9, 7, 4
641
dw 3, 5, 21, 17
C dw 8, 3, 5, 2
D dw 6, 1, 1, 4
G dw 9, 11, 2, 5
BT dw NLIN dup (NCOL dup(?)) ; B transpus
A2 dw NLIN dup (NCOL dup(0)) ; patratul lui A
SUMA dw NLIN dup (NCOL dup(0)) ; A^2+B’
VP dw NCOL dup(0) ;vector produs
VS dw NCOL dup(0) ;vector suma
i dw ?
j dw ?
ends

Variabilele de lucru ale fiecărei proceduri se definesc între instrucţiunile


RET şi ENDP.
Programatorul şef stabileşte modul de transmitere a parametrilor şi de
utilizare a regiştrilor, după cum urmează:
 matricele şi vectorii se folosesc ca variabile globale;
 pentru referirea elementului aij al masivului bidimensional se foloseşte
macrodefiniţia:

emw macro a, n, i, j, poz


push ax
push bx
mov ax, i
mul ax, n
mov bx, j
add ax,bx
shl ax, 1
mov bx, offset a
add ax, bx
mov poz, ax
pop bx
pop ax
endm

 pentru referirea elementului bj al unui masiv unidimensional se


foloseşte macrodefiniţia:

evw macro b, j, poz


push ax
push bx
mov ax, offset b
mov bx, j
shl bx, 1
add ax, bx
mov poz, ax
pop bx
pop ax
642
endm

 se foloseşte adresarea indirectă în care expresia de adresare este zonă de


memorie

Programatorii lucrează simultan la scrierea procedurilor. Astfel,


programatorul P1 scrie procedura pentru adunarea elementelor a doi vectori,
obţinând un al treilea vector:

Vsi:=Vpi+D
unde i=1,NCOL

Pentru procedurile de efectuare a produsului scalar, programatorul P2


elaborează în paralel cu ceilalţi componenţi ai echipei procedura:

prodscal proc
mov cx, NCOL-1
mov i,0
mov f,0
mov f+r, 0
ciclu: evw vs,i, poz
mov ax, [poz]
evw g, i, poz
mul [poz]
add f, ax
adc f+2, dx
inc i
loop ciclu
ret
endp

Pentru transpunerea unei matrice, programatorul P3 scrie textul sursă:

transp proc
push ax
push cx
mov ax, NLIN
mov i, 0
ciclu1:
push cx
mov j, 0
mov cx, NCOL
ciclu2:
emw b, i, j, NCOL, poz
mov ax, [poz]
emw bt, j, i, NLIN, poz
mov [poz], ax
inc j
643
loop ciclu2
pop cx
inc i
loop ciclu 1
pop cx
pop ax
ret
endp

Pentru adunarea a două matrice AP şi BT programatorul P4 scrie procedura:

admat proc
push ax
push ax
mov ax, NLIN
mov i, 0
ciclu 1:
push cx
mov j, 0
mov cx, NCOL
ciclu 2:
emw AP, i, j, NCOL, poz
mov ax,[poz]
emw BT, i, j, NCOL, poz
add ax,[poz]
emw MS, i, j NCOL, poz
mov [poz], ax
inc j
loop ciclu 2
pop ax
inc d
loop ciclu 1
ret
endp

Programatorul şef scrie programul apelator:

call prodmat
call transp
call admat
call prodmv
call advec
call prodscal
call convert
call afisir
mov ax, 4c00h
int 21h
end start
644

Acest mod de a concepe un program vizează lucrul în paralel, figura 19.2,


intercalând apelurile cu instrucţiuni de stocare pe stivă a listelor de parametri.

A1
A2
A3
A1 A4 Ax+1
A5
A6
A7

Figura 19.2 – Paralelizarea programelor


În figură:

 a1 – definire segmentul de date


 a2 – elaborare procedura 1
 ………………………………
 at – elaborare procedura n
 at+1 – elaborează programul apelator

Decizia de a utiliza variabile globale în proceduri restrânge


gradul de generalitate al procedurilor dar elimină lucrul pe stivă
cu parametrii simplificând expresiile de referire ale operanzilor.

19.4 Definiri distincte pentru toate componentele (m, k, n)

În cazul aplicaţiilor deosebit de complexe programatorul şef defineşte


structura pe module şi variabile, însă nu mai poate impune restricţii severe
colaboratorilor în ceea ce priveşte variabilele intermediare şi structurarea
modulelor la nivel de proceduri şi secvenţe.
Mai mult, pentru a lăsa independenţă în procesul de testare a modulelor este
necesar ca fiecare programator să-şi definească elementele specifice părţii din
aplicaţie aşa fel încât să delimiteze precis momentul de început al lucrului,
respectiv, momentul de predare al modulelor. În acest fel se creează posibilitatea
645

trecerii la altă aplicaţie şi realizarea departajării programatorilor după nivelul


performanţei individuale. Aplicaţia complexă presupune:
 distribuirea modulelor;
 construirea de segmente de date;
 construirea de segmente de stivă;
 construirea de segmente cu proceduri şi cu program de test;
 stabilirea corelaţiilor dintre programatori şi segmentele elaborate;
 stabilirea interdependenţei dintre proceduri;
 stabilirea interdependenţelor dintre segmentele de date şi stive, respectiv
proceduri.
Se consideră aplicaţia ‘GESTIUNE MATERIALE’ care conţine următoarele
funcţii, proceduri:
Pcre – creare fişier materiale prin adăugare;
Pdel – ştergere articol din fişier;
Padd – adăugare articol;
Pins – inserare articol;
Psort – sortare fişier;
Pmodif – modificare denumire material;
Pscrie – scrie articol în fişier;
Pmdpr – modificare preţ material;
Pindv – identificare articol după o cheie;
Paprv – aprovizionare cu o cantitate;
Pies – ieşiri de materiale;
Psupra – afişarea materialelor în stocuri supranormative;
Pfără – afişarea stocurilor fără mişcare;
Pzero – afişarea materialelor cu stoc zero;
Pval – calculul stocului final în expresie valorică.
Pentru rezolvarea acestei aplicaţii se procedează astfel:

a) se defineşte structura articolului în segmentul SEGART


SEGART SEG
mat struc
cod dw ?
den db 30 dup (?)
UM dw ?
Pret dw ?
Stocin dw ?
Intrari dw ?
Iesiri dw ?
Val dw ?
Ends

b) Se stabilesc datele de test sub forma tabelului 19.1


646

Tabelul 19.1. Datele de test ale aplicaţiei ‘GESTIUNEA


MATERIALELOR’
Cod Stoc Stoc Valoa
Denumire UM Preţ Intrari Ieşiri
matrial iniţial final re
1289 Tablă Foi 1200 25 12 5 32 38400
5423 Cuie Kg 150 10 15 7 19 2850
4566 Sârmă Kg 230 50 20 25 45 10350
3689 Cărămidă Buc 50 500 700 300 900 45000
TOTAL 96600

c) Se stabilesc intrările şi ieşirile modulelor.


 Pdel are ca intrare: nume fişier, codul materialului care va şters (cheia)
Proceduri apelate:
 Pindex pentru localizare articol după cheie;
 Pscrie pentru rescrierea articolului dezactivat;
d) se scrie procedura pentru creare prin adăugare de articol şi se încarcă
fişierul fişmat care este sursa de bază pentru toţi programatorii. Ei au
obligaţia de a-l copia şi de a-l utiliza în programele lor proprii.
e) se scrie procedura de afişare a fişierului.
f) se scrie procedura de numărare a articolelor din fişier şi de afişare a
numărului de articole.
Fiecare îşi defineşte şi structurile proprii în segmente. În final există n
proceduri şi m segmente de date.
Programatorul şef asamblează şi scrie programul apelator.
Se procedează la efectuarea testării:
o testarea modulelor (de către realizatori)
o testarea produsului asamblat.

19.5 Structuri liniare de program

Aplicaţiile sunt văzute ca funcţii de prelucrare care se execută obligatoriu,


una după cealaltă.
Se consideră funcţiile de prelucrare f1, f2, …, fn care alcătuiesc o aplicaţie.
Graficul asociat structurii liniare de program este dat în figura 19.3.

f1 f2 fn-1 fn

● ● ● ● ●
Figura 19.3 – Structură liniară de program
Acestei structuri îi corespunde secvenţa de program:
647

...
call f1
call f2
...
call fn
mov ax,4c00h
int 21h
f1 proc
...

ret
endp
f2 proc
...
ret
endp
...
fn proc
...
ret
endp

Pentru asigurarea traversării secvenţei celor n instrucţiuni call se


procedează la definirea unui vector de pointeri spre funcţii vpf prin linia sursă:

vpf dw f1, f2, f3


dw f4, f5, f6
...
dw fn-2, fn-1, fn

care se referă prin secvenţa:

mov si, offset vpf


mov cx, n
ciclu:
call [si]
inc si
in si
loop ciclu
mov ax, 4c00h
int 21h

Structura liniară corespunde unei singure succesiuni de apelare a


procedurilor. Procedurile sunt referite în totalitate şi strict în ordinea impusă,
indiferent de context.

19.6 Structura arborescentă


648

Se consideră funcţiile de prelucrare: f0 f1 f2 f3 … fn. Referirea funcţiilor se


efectuează selectiv după cum sunt îndeplinite o serie de condiţii.
Fie şirul de elemente v1 v2 … vm definite pe mulţimea booleană unde
vi{o,1} i=1, 2,…, m. Se construieşte structura arborescentă din figura 19.4.

f0

vi

fi fj

Figura 19.4 – Structura asociată nodului rădăcină corespunzătoare programului


principal (funcţia , f0)

Nodurile corespund apelurilor de funcţii(proceduri)

call fi
...
call fj

şi testelor pentru verificarea unor condiţii.

cmp vi, 0
jz apelj
apeli:
call fi
jmp ...
...
apelj:
call fj
...
În structura arborescentă asociată unui program se găsesc noduri interne
având forma dată în figura 19.5.

fk

vk
649

Figura 19.5 – Secvenţa de structură arborescentă asociată


unui apel de funcţii fk – nod intern al structurii
arborescente
În structura arborescentă există funcţii ‘terminale’ care corespund nodurilor
frunză. Ele nu apelează alte funcţii şi corespund execuţiilor ce preced încheierea
programului, figura 19.6.

fi fj fk

Figura 19.6 – Funcţiile terminale

Se consideră variabile funcţiile f0 f1 f2 f3 f4. Se construiesc perechile


(f1, v=1)
(f2, v=2)
(f3, v=3)
(f4, vR\1, 2, 3)
(f0, )
Se asociază acestui sistem de perechi structura
arborescentă din figura 19.7.

f0

Vk=1

f1
Vk=2

f2
Vk=3

f
650

Figura 19.7 – Structura arborescentă de program cu


funcţie cu condiţie simplă

f0
V1=1

V2=1 V2=1

V3=1 V4=1 V3=1 V4=1

f1 f2 f3 f4 f5 f6 f7 f8

Figura 19.8 – Structură arborescentă cu selecţia


procedurilor de tip nod terminal
Programul apelator pentru structura dată în figura 19.8, f0, se structurează
astfel:
start:
mov ax, @data
mov ds, ax
cmp v1, 1
jz salt_11
cmp v2, 1
651
jz salt_21
cmp v3, 1
jz salt_23
call f1
jmp final
salt_23:
call f2
jmp final
salt_21:
cmp v4, 1
jz salt_31
call f3
jmp final
salt_31:
call f4
jmp final
salt_11:
cmp v2, 1
jz salt_22
cmp v3, 1
jz salt_32
call f5
jmp final
salt_32:
call f6
jmp final
salt_22:
cmp v4, 1
jz salt_3y
call f7
jmp final
salt_3y:
call f8
final: mov ax, 4c00h
int 21h
end start

pentru a evita scrierea secvenţei:

mov ax, 4c00h


int 21h

după fiecare apel de forma:

call fi ;i=1, 2, …, 8

s-a folosit instrucţiunea:

jmp final
652

f1 f2 f3 f4 f5 f6 f7 f8

final: mov ax, 4c00h

int 21h

Figura 19.9 – Structura arborescentă cu încheierea


execuţiei într-un singur punct
În cazul în care selecţia procedurilor se efectuează după o variabilă ale cărei
valori sunt a1, a2, …, ak, structura arborescentă are graful asociat prezentat în figura
19.10.

f0

V=a1
Vk=a2
Vk=a3

Vk=an

fn+1 fn f3 f2 f1

Figura 19.10 – Structura arborescentă de selecţie după o


singură variabilă de control, cu toate procedurile de tip
terminal
Programul asociat este:
...
cmp v, a1
jz salt_1
cmp v, a2
jz salt_2
653
cmp v, a3
jz salt_3
...
cmp v, an
jz salt_n
call fn1; fn1=fn+1
mov ax, 4c00h
int 21h
salt_1:
call f1
mov ax, 4c00h
int 21h
salt_2:
call f2
mov ax, 4c00h
int 21h
...
salt_n:
call fn
mov ax, 4c00h
int 21h
f1 proc
...
ret
endp
f2 proc
...
ret
endp
...
fn proc
...
ret
endp
fn1 proc
...
ret
endp
end nume_program

Program calcul salarii A


- creare bază de date B
- calcul salarii C
- actualizare D
- afişare rezultat E

- Creare bază de date B


- după fişiere existente B1
- cu introducere de la tastatură B2
654

- prin scanare B3
- concatenare de fişiere B4

- Calcul salarii C
- pentru un salariat C1
- pentru salariaţii unui compartiment C2
- pentru toţi salariaţii C3

- Actualizare D
- modificare salariu brut D1
- modificare impozit D2
- modificare vechime D3
- modificare volum produse D4

- Afişare rezultate E
- pentru un salariat E1
- pentru toţi salariaţii E2
- afişare nume şi salariu net E3
- afişare toate datele E4

Figura 19.11 – Meniuri pentru programe de calcul salarii


Structurile arborescente organizate pe mai multe niveluri corespund
implementării interfeţelor cu regăsire a prelucrărilor din aproape în aproape. De
exemplu, se consideră meniurile din figura 19.11.
Pentru realizarea acestui meniu se dezvoltă structura
arborescentă din figura 19.12 organizată pe trei niveluri.
Selectarea se efectuează cu ajutorul unui mouse sau prin
tastarea literelor A, B, C, D, E respectiv B1, …, B4; C1, C2, C3; D1,
…, D4; E1, …E4 depinzând de nivelul pe care se efectuează
selecţia.

B C D E

B1 B2 B3 B4 C1 C2 C3 D1 D2 D2 D4 E1 E2 E3 E4
655

Figura 19.12 – Structura arborescentă organizată pe trei


niveluri
Structura de tip arborescent se caracterizează prin:
 apelul unic al unei funcţii (proceduri) în timpul lansării în
execuţie a programului;
 dispunerea condiţiilor şi funcţiilor (procedurilor) pe
niveluri;
 există un singur sens de apelare a funcţiilor
(procedurilor) de pe procedura părinte (de pe un nivel
superior) către procedura descendentă (de pe nivel
inferior) de pe nivelul imediat următor;
 programul are un singur program apelator;
 funcţiile (procedurile) nu apelează la rândul lor alte
proceduri.
Implementarea structurilor alternative multiple (GOTO
depending on–COBOL, GOTO calculat – Fortran, switch –
C/C++) permite dezvoltarea de structuri arborescente de
program complexe faţă de structurile binare bazate pe structura
IF – THEN – ELSE, figura 19.13.

f0

C1(1, 2, 3, 4)


C5(1, 2, 3, 4, 5)
1 2 3 4
C2(1, 2) C3(1, 2, 3, 4) C4(1)

f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12


656

Figura 19.13 – Structuri arborescente oarecare

Algoritmii de transformare a structurilor arborescente


oarecare, în structuri arborescente binare evidenţiază natura
binară a structurilor GOTO depending on, GOTO calculat sau a
structurii switch sau CASE ON prin care se implementează ca
secvenţe în modulele obiect rezultat
f0 al procesului de compilare.
Structurii din figura 19.13 îi corespunde structura binară din
figura 19.14. C1=1

C2=1 C1=2
C3=1
C1=3
f1 f2
C3=2 C5=1
f3
f7
C3=3 C5=2
f4
f8
C5=3
f5 f6
f9
C5=4
f10

f11
f12
657

Figura 19.14 – Structura binară asociată unei structuri oarecare


de program

19.7 Structuri de tip reţea

Se consideră funcţiile f0 f1 f2 …fn de prelucrare, din care f0


este nodul iniţial şi fn+1 este nodul final.
Succesiunea de apelare a procedurilor este dată de
rezultatul testelor. Se stabileşte prin convenţie ca rezultatul
adevărat (1) al condiţiei c să fie notat cu D, iar rezultatul fals (0)
să fie notat cu N, figura 9.1:

fk

C=1
N D
fi fj

Figura 19.15 – Rezultatul testării condiţiei c

Succesiunea de apel a procedurilor nu este restrictivă. O


procedură apelează la rândul ei proceduri care au mai fost
apelate. De asemenea, apelul unei proceduri este realizat din
mai multe locuri.
În figura 19.16 este prezentată o structură de tip reţea:
f0

C1
N D
f1 f2

C2 C3
N D N D
f3 f4 f6

C1:= C1+ f5
658

Figura 19.16 – Structură de program tip reţea

Secvenţele de proceduri apelate corespunzător structurii


de tip reţea dată în figura 19.16 sunt:
 f0 (f1 f3) (f1 f3)… (f1 f3) f1 f4 f5 f7
 f0 f1 f4 f5 f7
 f0 (f1 f3) (f1 f3)… (f1 f3) f2 f5 f7
 f0 (f1 f3) (f1 f3)… (f1 f3) f2 f6 f7
 f0 (f1 f3) (f1 f3)… (f1 f3) f4 f5 f7
 f0 f2 f5 f7
 f0 f2 f6 f7
În cazul în care se dezvoltă aplicaţii de tip ‘serial încrucişat’
(figura 19.17), se obţin, de asemenea, structuri de tip reţea.

f1 f2 f3 f4 f5

q1 q2 q3 q4 q5

Figura 19.17 – Structură de tip serial încrucişat

Se execută apelurile de proceduri:


 f1 f2 f3 f4 f5
659

 f1 f2 q3 q4 q5
 q1 q2 q3 q4 q5
 q1 q2 f3 f4 f5
Mai nou, se construiesc structuri de tip reţea cu reluarea
prelucrărilor de la toate punctele de prelucrare, figura 19.18:

f0 f1 C=1 f2 C=1 f3 C=1 fn

Figura 19.18 – Structură de tip reţea cu reluarea într-un punct


(f1)

Se utilizează operatorul ()k pentru a specifica repetarea de


k ori a secvenţei de apeluri a procedurilor incluse în paranteze.
Se execută următoare succesiuni de referiri ale
procedurilor:
 f0 (f1)k f2 … fn
 f0 (f1f2)k f3 … fn
 f0 (f1f2f3)k f4 … fn
 f0 (f1…fn-1)k fn
Acestea sunt considerate repetări simple. De asemenea
se execută succesiuni de repetări compuse, precum:

f0 (…(f1 f2 … fj)kr…)kr+1fn

De exemplu, secvenţa de repetări:

f0 (f1 f2 f3 (f1 f2 f3 f4 (f1 f2 )2 f3 f3 f5) f6 f7)3 … fn

se dezvoltă:
660

f0 f1 f2 f3 f1 f2 f3 f4 f1 f2 f1 f2 f3 f4 f5

Cu cât structura produsului este mai mare, cu atât se


impune găsirea unor modalităţi de simplificare a reprezentării
interacţiunilor dintre proceduri.

19.8 Concluzii

La soluţionarea unei probleme se va alege acea structură


care generează numărul cel mai mic de apeluri si de reveniri în
programele apelatoare. Cunoaşterea celor mai importante tipuri
de structuri de programe creează premisele comparării între
structuri cu proprietăţi care nu diferă semnificativ din punct de
vedere al performanţelor în vederea luării deciziei care să
propage la un număr cât mai mare de utilizatori efecte pozitive.

20
OPTIMIZAREA PROGRAMELOR
Optimizarea programelor reprezintă una din direcţiile spre care se
concentrează realizatorii de software. Există numeroase aspecte ale optimizării
programelor: optimizarea timpului de execuţie, optimizarea dimensiunii
operanzilor, optimizarea textului sursă etc. Toate aceste criterii de optimizare,
considerate cu diferite ponderi, contribuie la definirea conceptului de optimizare a
unui program.
Compilatoarele pentru limbajele evoluate (C, C++, Pascal etc) conţin
facilităţi de generare de cod executabil optimizat în timp de execuţie, în dimensiune
a codului, sau combinaţii între acestea. Datorită specificaţiilor de limbaj, la nivel de
asamblare nu există asemenea facilităţi încorporate în asambloare, de aceea
aspectele de optimizare revin în exclusivitate programatorului.
În materialul de faţă s-a tratat problema optimizării timpului de execuţie,
care survine uneori în defavoarea dimensiunii codului. S-a considerat această
abordare deoarece la arhitecturile actuale, memoria nu mai este, în general, o
resursă critică. Resursa critică devine timpul, mai ales la aplicaţiile de prelucrare în
timp real, la prelucrări grafice, la jocuri etc.
661

20.1 Criterii de optim

Se consideră un număr de programe destinate rezolvării aceleiaşi probleme.


Dintre toate, unul singur conduce la obţinerea rezultatului în timpul cel mai scurt.
În raport cu criteriul timpul de rulare, acest program este optim, raportat la
mulţimea de programe disponibile la un moment dat.
Întrucât nu se poate vorbi de program optim în general, în continuare, prin
program optim se înţelege acel program care are comportamentul cel mai bun,
chiar şi numai statistic, pe baza unui eşantion de probleme rezolvate.
Introducând sau eliminând programe sau seturi de probleme de test, este
posibil ca programul optim să fie de fiecare dată altul.
Criteriile după care se ierarhizează programele sunt:
 durata de timp necesară rezolvării unei probleme;
 dimensiunea problemei de rezolvat;
 necesarul de memorie internă;
 precizia rezultatelor;
 nivelul de generalitate al problemei ce poate fi acceptată;
 costul rulării programului;
 nivelul unei caracteristici de calitate;
 lungimea textului programului.
Criteriile sunt contradictorii, ceea ce determină ca la obţinerea unor
avantaje din punct de vedere ale unui criteriu să se estimeze care sunt efectele
negative antrenate pentru celelalte.
Programele scrise în limbajul de asamblare au particularităţi impuse de
faptul că programatorul gestionează toate resursele şi are posibilitatea să aleagă
dintre mai multe variante pentru orice construcţie pe care o realizează.
Toate criteriile sunt importante şi ideal este să fie construit programul care
le îndeplineşte pe toate. Se observă că lungimea textului, dimensiunea problemei,
precizia rezultatelor, calitatea şi necesarul de memorie sunt strâns legate de durata
execuţiei.
Estimarea duratei de rulare a unui program scris într-un limbaj evoluat este
dificilă. Optimizarea timpului de execuţie al unui program scris într-un astfel de
limbaj ţine de caracteristicile limbajului şi ale compilatorului. De asemenea,
compilatoarele evoluate suportă opţiuni pentru optimizarea dimensiunii codului sau
a duratei de execuţie, dar aceste optimizări, sunt, deocamdată, relative, deoarece se
fac la nivel local şi nu ţin cont de evoluţia în perspectivă a programului sau de
stările prin care s-a trecut anterior.
Dacă în cazul limbajelor evoluate complexitatea instrucţiunilor face dificilă
estimarea duratei, pentru limbajele de asamblare durata necesară execuţiei unei
instrucţiuni este dată cu precizie prin numărul de cicluri maşină.
662

Dacă programatorul face opţiuni din aproape în aproape asupra


instrucţiunilor selectate, căutând să reducă numărul de cicluri, va obţine un
program bun în raport cu durata de execuţie. Optimalitatea programului trebuie
demonstrată prin compararea cu alte programe sau chiar cu alte variante de
secvenţe destinate respectivului program şi este privită ca îmbunătăţire.
Optimizarea timpului de execuţie se pretează la sistemele cu capacitate
mare de memorie care necesită prelucrarea unei cantităţi de informaţie foarte mare
în timp critic (de exemplu, afişarea unor imagini dinamice pe un display grafic de
rezoluţie mare).
În cazul unor sisteme cu resursa de memorie de cod redusă şi care
procesează un volum de date mai modest, se pune problema optimizării
dimensiunii codului (de exemplu, procesoare pentru urmărirea unor instalaţii cu
parametrii lent variabili în timp).
În continuare se analizează modalităţi de optimizare a timpului de execuţie.

20.2 Cicluri maşină

În general, mecanismele au o componentă principală care dezvoltă o


operaţie de bază, esenţială pentru funcţia pentru care au fost realizate. Strungul are
un ax a cărui rotaţie determină antrenarea celorlalte subansamble. Operaţia de bază
este rotaţia completă. În cazul altor maşini, operaţia de bază este efectuarea
completă a unei deplasări dute-vino. Ciclul maşină este fie o rotaţie completă, fie o
mişcare rectilinie dus-întors.
Calculatoarele sunt înzestrate cu circuite de tact care definesc coordonatele
în timp ale operaţiilor ce se efectuează. Astfel, un calculator are un ciclu maşină de
durata dată prin împărţirea unei microsecunde la numărul de MHz ai ceasului său.
Un calculator 80286/10 MHz are un ciclu de 100 nanosecunde; un
calculator 80286/5 MHz are un ciclu de 200 nanosecunde; un calculator
80486/50MHz are un ciclu de 20 nanosecunde.
Când se proiectează un limbaj de asamblare se stabilesc implementările
fizice ale instrucţiunilor, precizându-se cu exactitate numărul de cicluri necesare
efectuării lor.
Astfel, pentru procesoare 8088, când operanzii sunt specificaţi, numărul de
cicluri este fix. De exemplu, instrucţiunea:

mov ax, 100

se execută în 4 cicluri, iar instrucţiunea:

mov ax, cx

se execută în 2 cicluri.
663

Când operanzii sunt variabile a căror poziţie se calculează prin evaluarea


unei expresii, iar poziţia lor efectivă este segmentul precizat sau adresa este un
număr impar deşi se lucrează la nivel de cuvânt, numărul de cicluri se majorează.
Instrucţiunea

mov ax, SUMA

se execută în 8+6 cicluri, expresia deplasamentului necesită pentru calculul adresei


6 cicluri. Când adresarea operandului este indirectă folosind fie registrul index, fie
registrul de bază, sunt necesare încă 5 cicluri. Astfel, instrucţiunea

mov ax, [bx]

se execută în 8+5 cicluri.


Dacă expresia pentru adresarea operandului conţine deplasare şi registru
index sau registru de bază, sunt necesari încă 9 cicluri: instrucţiunea

mov ax, VECTOR[bx]

se execută în 8+9 cicluri.


Expresia de calcul a adresei în care apar registrul de bază şi registrul index
necesită încă 7 cicluri, iar expresia care determină adresa pe bază de deplasare,
registru index şi registru de bază necesită încă 11 cicluri. Instrucţiunile :

mov ax, bx[si]


mov ax, MATRICE[bx][di]

necesită 8+7, respectiv, 8+11 cicluri.


Când sunt definite instrucţiunile, se specifică toţi factorii ce influenţează
numărul de cicluri. La prezentarea instrucţiunilor, în tabele există o coloană
destinată numărului de cicluri şi a variabilităţii.
Dacă se consideră factorii f1,f2,...,fk ce influenţează numărul de cicluri
pentru operaţii, după ce se determină exact contribuţia fiecărui factor, stabilind
coeficienţii c1, c2, ..., ck, modelul de calcul al numărului de cicluri pentru
instrucţiunea Ij este dat de ecuaţia:
NCI j  c 0 j  Sc i  x i
unde :
c0j - numărul de cicluri fixat pentru instrucţiunea I j;
xi - variabila booleană cu valoarea 1 dacă factorul fi este prezent, şi cu
valoarea 0 în caz contrar.
Dacă un factor vizează încărcarea de segment, coeficientul său asociat este
cu valoare 2. Dacă factorul de aliniere a adresei la nivel de cuvânt este prezent,
664

când operandul are adresa efectivă un număr impar, "forţarea" la baitul următor cu
adresa număr par presupune adăugarea a 4 cicluri. Instrucţiunea

mov SUMA, ax

se execută în 20 cicluri dacă 8 cicluri se datorează faptului că un operand este în


registrul AX iar celalalt este în memorie, 6 cicluri rezultă din calculul adresei de 16
biţi ai deplasamentului, 2 cicluri provin din încărcarea segmentului unde se află
variabila SUMA, aşa cum rezultă din definirea ei şi din punerea în corespondenţă a
segmentelor cu registrele de segment în directiva ASSUME şi 4 cicluri sunt
necesare pentru a forţa încărcarea unei adrese pare, pentru că variabila SUMA este
definită la o adresă impară.
Numărul de cicluri este influenţat de distanţa la care se face saltul
necondiţionat, dacă se lucrează în mod protejat sau nu, cât de lungă este zona de
memorie care se transferă, dacă se lucrează în cadrul aceluiaşi segment sau între
segmente diferite. Rezultatele pot fi scurte, în apropiere sau la distanţă.
Modelul de calcul al numărului de cicluri se obţine din tabelele de descriere
a instrucţiunilor cât şi din prezentarea fiecărei instrucţiuni în parte.
Menţinând clasificarea instrucţiunilor după numărul de cicluri, se preferă
acele instrucţiuni care nu depind de amplasamentul operanzilor sau se preferă acele
moduri de adresare care generează sistematic un număr redus de cicluri.
Instrucţiunea de salt necondiţionat din secvenţa:

..................
alfa: mov ax, 5
..................
jmp alfa

având distanţa de tip scurt necesită 1 ciclu, în timp ce un salt necondiţionat


intersegmente, folosind adresarea indirectă ca în instrucţiunea:

jmp beta [bx][di]

necesită 16 + 7 + 2 cicluri (c0j = 16, c1 = 7, c2 = 2).


În documentaţii se fac referiri detaliate asupra expresiilor de adresă care
intervin în calculul ciclurilor ce se asociază fiecărei variabile de instrucţiune.
Este important să se cunoască efectele contradictorii ale opţiunilor la
scrierea de programe în limbaj de asamblare. De regulă se obişnuieşte obţinerea
unui text compact prin definirea şi apelarea de proceduri. Se minimizează lungimea
textului dar fiecare apel de procedură înseamnă o instrucţiune CALL. Dacă
procedura este referită prin adresa în cadrul aceluiaşi segment, numărul de cicluri
necesar execuţiei este 11. Dacă se lucrează în cadrul aceluiaşi segment dar adresele
sunt pe 4 baiţi, pentru un apel de procedură sunt necesare 26 cicluri. Gestionarea
665

prin apeluri de proceduri a task-urilor sau lucrul în mod privilegiat conduce la un


necesar de cicluri de 177, respectiv, 90+4*x cicluri (x reprezintă numărul de
parametri). Orice procedură presupune revenirea în funcţia apelatoare folosind
instrucţiunea RET, care consumă între 11 şi 55 cicluri, de asemenea, depinzând de
tipul formulei de calcul a adresei instrucţiunii ce urmează lui CALL. Dacă această
adresă este stocată cu instrucţiunea POP/PUSH înainte de apel şi procedura este în
acelaşi segment în care se află funcţia apelatoare, sunt necesare 11 cicluri. Dacă
tipul de pointer este FAR sunt necesari 55 cicluri. Deşi lucrând cu proceduri s-a
obţinut reducerea lungimii textului, numărul de cicluri adăugat este de cel puţin 7 +
11 sau de cel mult 185 + 55 cicluri.

20.3 Volumul de operaţii

Un program scris în limbaj de asamblare conţine linii sursă care la


asamblare generează instrucţiuni executabile (maşină) şi linii sursă ce definesc
contextul de alocare resurse şi de iniţializare. Volumul de operaţii se referă la
instrucţiunile executabile.
Procedura:

aduna PROC
push bp
mov bp,sp
mov ax,[bp+4]
add ax,[bp+6]
add ax,[bp+8]
pop bp
ret
aduna ENDP

conţine şapte instrucţiuni executabile, fiecare se execută o singură dată. Volumul


de operaţii, ca număr de instrucţiuni ce se execută, în acest caz este chiar 7.
Procedura:

generare PROC FAR


push bp
mov bp,sp
push cx
push bx
mov cx,[bp+6]
mov bx,[bp+8]
mov ax,0
ciclu: add al,[bx]
adc ah,0
inc bx
loop ciclu
pop cx
666
pop bx
pop bp
ret
generare ENDP

conţine 14 instrucţiuni executabile, dintre care:

ciclu:
add al,[bx]
adc ah,0
inc bx
loop ciclu

se execută de un număr de ori dependent de conţinutul registrului CX. Presupunând


că registrul CX conţine o valoare notată generic n, volumul de operaţii executate la
apelul procedurii generare va fi dat de relaţia:

V  k0  n1  k1 ...  n p  k p

unde:
k0 -numărul instrucţiunilor executate o singură dată;
ni -numărul de repetări ale buclei de program i;
ki -numărul de instrucţiuni cuprinse în bucla de program i.
Valoarea sa este V=11+4*n.
Există proceduri în care apar comparaţii şi se efectuează selecţii ale
secvenţelor. Se notează P j probabilitatea ca o condiţie Cj să fie îndeplinită, caz în
care se execută o secvenţă având volumul Vt. Numărul repetărilor testului pentru
condiţia Cj este nj.
Volumul operaţiilor este dat de relaţia:
 
V  Pj  Vf  1  Pj  Vt  n j .
Volumul de operaţii apare ca un număr mediu de operaţii care se vor executa în
timp. Procedura:

trans PROC NEAR


mov al,[si]
cmp al,0
jz final
cmp al,'a'
jb urmat
cmp al,'z'
ja urmat
and al,NOT 20h
mov [si],al
urmat:
667
inc si
jmp trans
final:
ret
trans ENDP

converteşte un şir de lungime M caractere, terminat cu zero, într-un şir format


numai din litere mari. Volumul de operaţii depinde de structura şirului de caractere.
În structura textelor din limba română 6% dintre caractere sunt litere mari.
Procedura trans va efectua transformarea and al,NOT 20h în 94% din cazuri. La
stânga literelor mici se află 3,3 % dintre caracterele unui text, iar la dreapta restul
de 96,7 %, aşa cum rezultă din observaţii parţiale.
Pentru textul de lungime M caractere, delimitat prin zero, instrucţiunile
procedurii trans se execută în medie de un număr de ori indicat în tabelul 20.1.

Tabelul 20.1.
Instrucţiunea Număr de repetări mediu
Mov al,[si] M
Cmp al,0 M
jz final M
Cmp al,’a’ M
jb urmat 0.033*M
Cmp al,’z’ 0.967*M
ja urmat 0.967*M
And al,NOT 20h 0.96*M
Mov [si],al 0.96*M
Inc si M
Jmp trans M
Ret 1

Volumul mediu de operaţii executabile la un apel al procedurii trans este

V = M*(6+0.967+0.967+0.96+0.96+0.033)+1

operaţii.
Conceptul de operaţie este general şi se observă de la început că
posibilitatea de a compara operaţiile este dificilă datorită diferenţei de complexitate
668

pe care fiecare operaţie o induce. Un transfer de date, intuitiv este mai simplu decât
o înmulţire, iar apelul unei proceduri este mai complex decât o implementare.
Se acceptă ipoteza conform căreia complexitatea operaţiilor este strâns
legată de numărul de cicluri maşină asociate. Diversităţii de instrucţiuni îi
corespunde o multitudine de numere de cicluri. Mai mult, tipurile de adresare
modifică numărul de cicluri pentru fiecare instrucţiune. Instrucţiunile care
manipulează un volum redus de informaţie sau au operanzi prefixati sunt puse în
corespondenţă cu un număr redus de cicluri. Instrucţiunile care au adrese ce sunt
calculate după formule complexe, care impun regăsiri, necesită un număr de cicluri
maşină superior.
Pentru a reflecta mai exact efortul de execuţie, volumul programului se va
exprima ca număr de cicluri maşină. Astfel, secvenţa:
.......................
mov ds,ax ; 2 cicluri masina
xor ax,ax ; 3 cicluri masina
inc ax ; 2 cicluri masina
cmp ax,20 ; 3 cicluri masina
...............................

are un volum V = 10 cicluri, rezultat ca sumă a ciclurilor asociate instrucţiunilor


care o alcătuiesc.
Pentru procedura:

strlen PROC NEAR


cld ; 2 cicluri
push cx ; 10 cicluri
mov cx,0ffffh ; 4 cicluri
mov al,0 ; 4 cicluri
repne scasb ; n*(6+15) cicluri
jne eroare ; 8 cicluri pentru salt, 4 cicluri
fara salt
mov ax,0ffffh ; 4 cicluri
sub ax,cx ; 3 cicluri
dec ax ; 2 cicluri
dec di ; 2 cicluri
dec di ; 2 cicluri
jmp SHORT final ; 1 ciclu
eroare:
mov di,0 ; 4 cicluri
mov es,di ; 2 cicluri
final:
pop cx ; 8 cicluri
ret ; 8 cicluri
strlen ENDP

volumul de operaţii exprimat în cicluri este


V1  50  21  n
669

în caz de eroare sau


V2  24  21  n  30
cicluri.

20.4 Secvenţe echivalente

Dacă se urmăreşte optimizarea programelor scrise în limbaj de asamblare


reducând volumul operaţiilor, în primul rând se caută folosirea de secvenţe
echivalente care se execută într-un număr mai redus de cicluri.
Iniţializarea unui registru se realizează în moduri diferite. Din secvenţa:
mov ax,0 ; 4 cicluri
sub ax,ax ; 3 cicluri
xor ax,ax ; 3 cicluri

rezultă necesitatea de a utiliza una din ultimele două variante, deşi prima
instrucţiune este mai sugestivă.
La tipul de adresare indexat este necesară incrementarea unui registru cu o
raţie egală cu lungimea zonei de memorie care este referită. Dacă raţia este o
unitate, din secvenţa:

add si,1 ; 4 cicluri


inc si ; 2 cicluri

rezultă că este avantajoasă utilizarea instrucţiunii inc si, efectul fiind major mai
ales pentru faptul că referirea este proprie unei secvenţe executate repetitiv.
Dacă raţia cu care se modifică registrul index este un număr oarecare,
repetarea instrucţiunii inc registru este ineficientă. Se optează spre una din
variantele din secvenţa:

add si,57 ; ratia este 57, 4 cicluri


add si,bx ; 3 cicluri, registrul bx a fost
initializat mov bx,57
add si,ratia ; 16+6 cicluri

De cele mai multe ori nu este posibilă alocarea unui registru pentru
memorarea raţiei şi se defineşte o constantă simbolică ( RATIA EQU 57) după care
incrementarea este realizată prin add si,RATIA.
Lucrurile devin mult mai simple dacă se pune problema alegerii modalităţii
de a înmulţi un număr cu 2k sau de a-l împărţi prin 2k. Pentru înmulţirea numărului
79 aflat în registrul AX cu 32 se alege secvenţa:

mov cl,5
mov ax,79
670
sal ax,cl ; 8+4*5 cicluri

întrucât secvenţa:

mov bl,32
mov al,79
cbw b ; 2 cicluri
mul bl ; 71 cicluri

necesită mai multe cicluri.


În cazul în care o procedură este apelată de un număr mare de ori, este
preferabil să se includă textul ei în locul instrucţiunii call. Chiar dacă lungimea
textului sursă creşte, se obţine o reducere a numărului de cicluri generate de fiecare
apel şi de fiecare revenire în secvenţa apelatoare.

20.5 Alegerea tipului de dată

În limbajele evoluate lucrul cu diferite tipuri de date este netransparent,


programatorul neavând la dispoziţie resursele antrenate. În programul scris în
limbaj de asamblare lucrul cu date codificate binar înseamnă a defini variabile la
nivel de bait sau pe cuvânt. Odată definită o variabilă la nivel de bait, se vor utiliza
registrele AL, BL, CL, DL, AH, BH, CH, DH. Lucrul la nivel de cuvânt înseamnă
lucrul cu registrele AX, BX, CX, DX. În ambele cazuri se are în vedere testarea
indicatorului de condiţie CF pentru a gestiona corect rezultatele. Se pot folosi
instrucţiunile setului, ca şi cum limbajul de asamblare este proiectat preponderent
pentru a lucra cu numere codificate binar.
Dacă se doreşte să se lucreze în aritmetica zecimală, mai întâi se construiesc
toate procedurile care operează în această aritmetică: transferuri între operanzi,
adunări, scăderi, înmulţiri, împărţiri, deplasări, alinieri. Procedurile vor fi cu un
grad ridicat de generalitate. Evaluarea unei expresii va consta mai întâi în
demontarea ei în paşi elementari, se pregătesc parametrii şi se vor apela funcţiile
pentru efectuarea operaţiilor. Programatorul va gestiona şi rezultatele elementare şi
pe cele intermediare.
Dacă se lucrează în virgulă mobilă, fiecare limbaj de asamblare admite un
set de instrucţiuni specifice acestui tip de date şi chiar registre specializate.
În procesul de optimizare a programelor scrise în limbaj de asamblare,
alegerea tipului de date este esenţială pentru efortul de programare în primul rând.
Lucrul cu date de tip real presupune utilizarea resurselor unui coprocesor, iar
aritmetica zecimală e folosită când operanzii au un număr foarte mare de cifre şi
sunt întregi.
671

Neomogenitatea operanzilor este aproape exclusă în programele scrise în


limbaje de asamblare.
Dacă un operand este definit pe un bait şi participă la evaluarea unei
expresii în care un rezultat intermediar este în registrul AX, la nivel de cuvânt, este
necesară o conversie de la bait la cuvânt pe care o asigură programatorul. În
programele scrise în limbaj de asamblare construcţiile implicite sunt foarte rare.
Secvenţa:

mov bx,ax
mov al,operand_bait
cbw
add ax,bx

ilustrează faptul că programatorul gestionează şi rezultatele intermediare. Problema


devine mai complicată când un operand este întreg iar altul este zecimal
împachetat. Mai întâi se ia decizia cum se va lucra în continuare. Dacă se va
continua lucrul în zecimal împachetat, operandul binar va fi convertit apelând la o
procedură la forma de reprezentare zecimal împachetată. În continuare se vor apela
proceduri de lucru pentru zecimal împachetat.
Dacă opţiunea este de a lucra în binar, operandul zecimal împachetat va fi
convertit în binar şi se vor folosi în continuare instrucţiunile setului definit pentru
limbajul de asamblare considerat.
În cazul în care este necesar să se lucreze în virgulă mobilă cei doi operanzi
vor fi convertiţi folosind proceduri speciale şi cu instrucţiunile de virgulă mobilă se
va continua lucrul.
Lucrul cu operanzi neomogeni impune existenţa unei multitudini de
proceduri de conversie care să acopere totalitatea cerinţelor de omogenizare a
tipurilor. După omogenizare, programatorul va folosi numai instrucţiunile sau
procedurile specifice tipului pentru care s-a hotărât să continue lucrul.
Optimizarea programului este obţinută în această fază prin numărul de
proceduri de conversie ce se apelează şi prin procedurile de operaţii. Un
programator cu experienţă va şti când să nu folosească aritmetica binară,
problematica alegerii tipului cel mai potrivit fiind destul de rară. Programatorul îşi
defineşte din start operanzi omogeni şi exclude efectuarea de conversii tocmai
pentru că neomogenitatea este transparentă în programele scrise în limbaj de
asamblare prin creşterea lungimii datorate secvenţelor suplimentare specifice
omogenizării.
Chiar dacă procedurile de conversie sunt rezultatul unui proces de
optimizare, adăugarea lor la un program determină creşterea volumului de operaţii.

20.6 Eliminarea subexpresiilor comune


672

Limbajele evoluate au tendinţa de a reda forma algebrică a expresiilor. De


aceea programatorul îşi pune distinct problema eliminării subexpresiilor comune.
Expresia:

2
E = (a+b+c) * (a+b+c-d) + (c +a+b+c) / (a+b+c)

va fi scrisă de cele mai multe ori direct cum apare şi în rare cazuri se va calcula
E1=a+b+c după care se va calcula:

2
E = E1 * (E1-d) + (c +E1) / E1.

Când se scrie programul în limbaj de asamblare, demontarea expresiei şi


reaşezarea rezultatelor intermediare pentru a respecta priorităţile operatorilor îl
determină pe programator să urmărească simultan şi modalităţi de a reduce
lungimea secvenţei. Eliminarea subexpresiilor comune apare ca o necesitate
firească pentru programator. Mai mult, el va căuta să gestioneze cu grijă rezultatele
intermediare, lungimile zonelor de memorie asociate lor, pentru a nu deteriora
omogenitatea operanzilor. Secvenţa:

mov ax,a ;5
add ax,b ;7
add ax,c ;7
mov E1,ax ;3
sub ax,d ;7
mul E1 ;24
mov prod1,ax ;3
mov prod1+2,dx ;3
mov ax,c ;5
mul c ;24
add ax,E1 ;7
adc dx,0 ;7
div E1 ;25
xor dx,dx ;2
add ax,prod1 ;7
adc dx,prod1+2 ;7
mov E,ax ;3
mov E+2,dx ;3 total 149 cicluri (80286)

ţine seama de ceea ce conţin registrele după efectuarea operaţiilor şi utilizează acest
conţinut. Este puţin probabil ca programatorul să repete de patru ori secvenţa:

mov ax,a
add ax,b
add ax,c
673

în programul său, fără ca cel puţin să se gândească la scrierea unei macrodefiniţii


pentru a-şi uşura efortul de a scrie textul sursă.

20.7 Gestionarea corectă a invarianţilor

În secvenţele care se execută repetitiv, din eroare sunt introduse iniţializări


ale variabilelor care alterează rezultatele finale. Secvenţa:

mov cx,22
ciclu:
mov ax,0
mov si,0
add ax,x[si]
inc si
loop ciclu
mov total,ax

va conduce nu la însumarea elementelor unui vector, ci la însumarea primei


componente a vectorului, numai. Cele două iniţializări de registre, invarianţii din
secvenţă, sunt scoşi în afară, obţinând variabilitatea cerută de orice structură
repetitivă. Secvenţa:

mov ax,0
mov si,0
mov cx,22
ciclu:
add al,x[si]
inc si
loop ciclu

realizează corect obiectivul propus.

20.8 Regruparea ciclurilor

În cazul secvenţelor repetitive în care rezultatele sunt independente,


numărul de repetări este identic, secvenţele se regrupează reducând numărul
operaţiilor induse de testul variabilei de control şi de modificarea acesteia.
Secvenţa:

mov cx,37 ; 4 cicluri


xor ax,ax ; 3 cicluri
xor si,si ; 3 cicluri
ciclux:
add al,x[si] ;18 cicluri * 37
inc si ; 2 cicluri * 37
loop ciclux ; 37 * 9 cicluri sau 5 cicluri
674
mov suma,ax ; 14 cicluri
mov cx,37 ; 4 cicluri
xor ax,ax ; 3 cicluri
xor si,si ; 3 cicluri
cicluy:
add al,y[si] ; 18*37 cicluri
inc si ; 2*37 cicluri
loop cicluy ; 9*37 sau 5 cicluri
mov sumy,ax ; 14 cicluri

se execută într-un volum

V=10+29*37+24+10+29*37+14=2204

de cicluri. Secvenţa în care se regrupează ciclurile:

mov cx,37 ; 4 cicluri


xor si,si ; 3 cicluri
xor ax,ax ; 3 cicluri
xor bx,bx ; 3 cicluri
ciclu:
add al,x[si] ; 18*37 cicluri
add bl,y[si] ; 18*37 cicluri
inc si ; 2*37 cicluri
loop ciclu ; 9*37 cicluri sa 5 cicluri
mov sumax,ax ; 14 cicluri
mov sumay,bx ; 14 cicluri

necesită un volum

V=13+37*47+5+28=1785

de cicluri (8088).
Dacă numărul de elemente ale unui şir este par se poate înjumătăţi numărul
de repetări prin calculul a două sume (suma elementelor cu poziţie pară şi suma
elementelor cu poziţie impară). La ieşirea din ciclu printr-o însumare se obţine
rezultatul dorit.
Secvenţa:

mov cx,2*N ;4 cicluri, N constanta simbolica


xor ax,ax ; 3 cicluri
xor si,si ; 3 cicluri
ciclu:
add ax,x[si] ; 18*2*N cicluri
inc si ; 2*2*N cicluri
inc si ; 2*2*N cicluri
loop ciclu ; 9*2*N cicluri sau 5 cicluri
mov sum,ax ; 14 cicluri
675

necesită un volum de operaţii V=29+2*N*31 cicluri.


Prin însumarea separată a elementelor cu poziţii pare, respectiv impare,
secvenţa:

mov cx,N
xor ax,ax
xor bx,bx
xor si,si
ciclu: add ax,x[si]
add si,2
add bx,x[si]
add si,2
loop ciclu
add ax,bx

necesită un număr de operaţii V=13+2*N*26+N+8. Comparând cele două volume,


rezultă o diferenţă D=8+9*N cicluri, ceea ce justifică o astfel de regrupare a
termenilor din structurile de date omogene.

20.9 Eliminarea secvenţelor inconsistente

Uneori în programe se introduc instrucţiuni care anulează efectele


operaţiilor precedente. Programatorul va elimina acele instrucţiuni care nu
corespund cerinţelor şi distrug rezultate create anterior. Eliminarea de instrucţiuni
atrage reducerea volumului de operaţii.
În secvenţa următoare:

mov bx,0 ;1
xor ax,ax ;2
add ax,2 ;3
mov bx,ax ;4

instrucţiunea 1 este inconsistentă, deoarece registrul BX este apoi modificat


(instrucţiunea 4) fără ca valoarea stocată în el anterior să fie folosită. Pentru
limbajele evoluate (C, Pascal) la compilare există posibilitatea ca utilizatorul să fie
avertizat asupra variabilelor nefolosite. În asamblare o asemenea analiză la
compilare este cvasi-imposibilă, de aceea programatorul trebuie să depisteze
asemenea secvenţe încă din faza de scriere a codului sursă.

20.10 Eliminarea secvenţelor inactive (cod mort)

Programele scrise în limbaje evoluate pot conţine secvenţe ce nu se


activează indiferent de contextul în care se rulează programul. În programele scrise
676

în limbaj de asamblare, pentru a reduce deplasarea operanzilor, la definire aceştia


sunt incluşi în segmentul program, ca în secvenţa:

.CODE
start:
jmp alfa
x dw 10
y dw 20
z dw ?
alfa:
mov ax,x
add ax,b
mov z,ax
mov ah,4ch
int 21h
END start

Astfel de instrucţiuni sunt frecvente şi corecte, poate chiar eficiente, şi de


aceea este dificil a se identifica secvenţele care nu se activează niciodată în
execuţie.
Există şi situaţii când se construiesc teste în mod eronat, fără a asigura
încheierea unui ciclu sau existenţa cel puţin a unei situaţii în care se traversează şi o
altă ramură a structurii alternative.
Expresiile booleene construite în limbajele evoluate pot conduce la evaluare
la valori constante indiferent de variaţiile operanzilor. Complexitatea expresiilor şi
manipularea eronată a operatorilor generează secvenţe numite cod mort, adică
secvenţe ce nu se execută niciodată. În programele scrise în limbaj de asamblare
astfel de construcţii apar ca incorecte relativ uşor, întrucât se identifică
invariabilitatea operanzilor.
Secvenţa:

mov ax,5
cmp ax,0
jz alfa
..............
jmp beta
alfa:
.........
beta:
nop

este interpretată ca generatoare de cod mort dacă este inclusă chiar într-o structură
repetitivă, pentru că atât timp cât ax va conţine 5 şi se va compara cu valoarea zero,
secvenţa etichetată cu alfa nu se va executa. Volumul de operaţii nu este influenţat
dacă se ia în considerare coeficientul zero al probabilităţii acestei secvenţe inactive.
677

Codul mort afectează numai lungimea programului ca număr de baiţi ocupaţi de


codul obiect asociat unui text sursă.

20.11 Reacoperirea segmentelor

Segmentele se gestionează de către programator. Directivele de punere în


corespondenţă a segmentelor cu registre de segmente, încărcarea adreselor de
segment, sunt elemente la dispoziţia programatorului. Instrucţiunile de salt
necondiţionat se diferenţiază după cum destinaţia este în acelaşi segment sau este
în alt segment, adresarea fiind directă sau indirectă. Apelul de procedură din acelaşi
segment are 7 sau 11 cicluri, în timp ce pentru proceduri din alte segmente numărul
de cicluri poate fi 13 sau 26 cicluri.
Optimizarea reacoperirii vizează programe complexe, care operează cu
structuri de date ce necesită mai multe segmente de date care se încarcă alternativ.
Pentru proceduri se va asocia o arborescenţă pentru a se identifica ce componente
se află încărcate de pe fiecare ramură.

20.12 Alocarea optimă a regiştrilor

Alocarea regiştrilor este o problemă de construire a compilatoarelor.


Aceeaşi secvenţă se va utiliza în moduri diferite, obţinându-se de fiecare dată alt
număr de cicluri maşină.
Alocarea regiştrilor are ca obiectiv minimizarea numărului de cicluri. Se
vor construi compilatoare care realizează alocări de registre şi tipuri de adresări
care să conducă la atingerea acestui obiectiv. De exemplu, pentru evaluarea
expresiei:

e=a+b+c

se construieşte secvenţa:

mov ax,a ;5 cicluri


mov bx,b ;5 cicluri
mov cx,c ;5 cicluri
add ax,bx ;2 cicluri
add ax,cx ;2 cicluri
mov e,ax ;3 cicluri

căreia îi corespund 22 cicluri maşină. Secvenţa echivalentă:

mov ax,a ;5 cicluri


add ax,b ;7 cicluri
add ax,c ;7 cicluri
mov e,ax ;3 cicluri
678

conţine instrucţiuni care totalizează tot 22 cicluri.


Problematica alocării este importantă pentru operanzii reutilizabili din
expresii. Astfel, expresia:

e=(a+b-c)*(a-b+c)

se calculează în secvenţa:

xor dx,dx ;2 cicluri


mov ax,a ;5 cicluri
sub ax,b ;7 cicluri
add ax,c ;7 cicluri
mov bx,ax ;2 cicluri
mov ax,a ;5 cicluri
add ax,b ;7 cicluri
sub ax,c ;7 cicluri
mul bx ;21 cicluri

căreia îi corespund 63 cicluri. În secvenţa echivalentă:

xor dx,dx ;2 cicluri


mov bx,b ;5 cicluri
mov cx,c ;5 cicluri
mov si,a ;5 cicluri
mov ax,si ;2 cicluri
sub ax,bx ;2 cicluri
add ax,cx ;2 cicluri
mov bx,ax ;2 cicluri
mov ax,si ;2 cicluri
add ax,bx ;2 cicluri
sub ax,cx ;2 cicluri
mul bx ;21 cicluri

se obţin 52 cicluri.
Numărul de cicluri necesare pentru execuţia unei instrucţiuni cu operanzi
din memorie este mai mare decât în cazul în care operanzii s-ar afla în regiştrii.
Pentru reducerea timpului de execuţie, se va urmări păstrarea rezultatelor
intermediare în regiştrii liberi, printr-o alocare optimă a acestora.
Conform acestui principiu, secvenţa pentru calculul expresiei :
2
E=(a+b+c)*(a+b+c-d)+(c +a+b+c)/(a+b+c)

devine, prin utilizarea regiştrilor BX, SI şi DI, următoarea:

mov ax,a ;5 cicluri


add ax,b ;7 cicluri
679
add ax,c ;7 cicluri
mov bx,ax ;2 cicluri expresia E1
sub ax,d ;7 cicluri
mul bx ;21 cicluri
mov di,ax ;2 cicluri
mov si,dx ;2 cicluri
mov ax,c ;5 cicluri
mul c ;24 cicluri
add ax,bx ;2 cicluri
adc dx,0 ;7 cicluri
div bx ;14 cicluri
xor dx,dx ;2 cicluri
add ax,di ;2 cicluri
adc dx,si ;2 cicluri
mov E,ax ;3 cicluri
mov E+2,dx ;3 cicluri total 117 cicluri

Se remarcă utilizarea regiştilor BX, SI şi DI pentru păstrarea unor rezultate


intermediare. Este clar că după această secvenţă, valorile iniţiale din aceşti regiştri
vor fi modificate. Dacă acestea sunt necesare pentru prelucrări ulterioare, se
salveză, fie în stivă cu instrucţiunea push, fie în variabile din memorie.
Utilizarea regiştrilor se face după o analiză a codului pe bază statistică,
astfel încât costul salvării şi refacerii regiştrilor folosiţi să fie mai mic decât costul
utilizării exclusiv a memoriei pentru salvarea rezultatelor intermediare.

20.13 Concluzii

Particularităţile limbajelor de programare se regăsesc la optimizare. Se


observă că optimizarea programelor scrise în limbaj de asamblare conţine acele
elemente ce impun simplitate secvenţelor de program. Criteriile de optim,
numeroase la celelalte limbaje de programare, se restrâng, atenţia fiind îndreptată
spre minimizarea volumului de operaţii.
Datorită faptului că programele scrise în limbaj de asamblare nu înlocuiesc
aplicaţii scrise în limbaje de tip C, ci le presupun, dacă minimizează numărul de
cicluri maşină, înseamnă că s-a realizat optimizare. Programatorul în limbaje de
asamblare are multe restricţii de utilizare a registrelor, a instrucţiunilor. S-a făcut
deosebire între optimizarea sistemelor de programe şi optimizarea pe textul sursă.
De aceea, aplicaţiile în scrise în limbaj de asamblare sunt de regulă parţi ce se
încorporează în construcţii mult mai complexe. Optimizarea va fi orientată spre
viteza de calcul în principal; reducerea lungimii programului, creşterea intensităţii
de utilizare a operanzilor trec pe un plan secundar.
Microprocesoarele evoluate (ex. i386, 486, Pentium, etc) sunt proiectate
astfel încât instrucţiunile sunt executate în pipeline. Aceasta presupune
suprapunerea unor stări disjuncte din execuţia unor instrucţiuni. Astfel, după
extragerea codului unei instrucţiuni şi trecerea la execuţia acesteia, se extrage în
680

paralel codul instrucţiunii următoare. Aceste particularităţi ţin exclusiv de


arhitectura microprocesorului pe care va rula programul. Se poate rafina
optimizarea timpului de execuţie luând în calcul aceste particularităţi, dar cu
referire strictă la un tip de procesor.

21
DESIGNUL LIMBAJELOR DE ASAMBLARE

21.1 Cerinţe ale designului

Limbajele de asamblare apar ca rezultat al proiectării microprocesoarelor.


Limbajele evoluate de programare permit accesul la toate resursele sistemelor de
calcul. Performanţa software este influenţată de modul în care a fost proiectat, de
limbajul în care este elaborat şi de facilităţile oferite de limbajul de asamblare, la
care se ajunge în final. Designul limbajelor de asamblare reprezintă un nou mod de
abordare a construirii acestora în vederea ameliorării performanţei software
aplicativ.
Limbajele evoluate de programare elimină suportul învăţării programării în
limbaj de asamblare pentru a avea acces la toate resursele sistemelor de calcul.
Utilizarea corectă a funcţiilor din bibliotecile standard din programele scrise în
limbajele C++ sau PASCAL oferă programatorilor posibilităţile de acces la orice
nivel al resurselor.
În acest nou context, studierea unui limbaj de asamblare se justifică pentru
eficientizarea unor secvenţe de program. Există posibilitatea de a introduce direct
în textele sursă C/C++ secvenţe asm.
O altă motivaţie este înţelegerea exactă a unor mecanisme de manipulare a
informaţiei (lucru pe stivă, lucru cu variabile pointer, definirea şi utilizarea
funcţiilor virtuale).
Dacă au existat perioade în care limbajul de asamblare a fost considerat un
produs natural, uneori imperfect, al procesului de proiectare al procesoarelor, acum
se pune şi problema de a realiza microprocesoare pornind de la un limbaj de
asamblare dat. Limbajul de asamblare este definit aşa fel încât să asigure eficienţa,
măsurată statistic, fie la implementarea unor mecanisme noi de gestiune a
memoriei, fie în generarea de secvenţe compacte, fie în reducerea duratei de
execuţie a prelucrărilor. În continuare sunt analizate aspecte de bază ale designului
limbajelor de asamblare. Programele scrise într-un limbaj de asamblare proiectat cu
luarea în considerare a anumitor criterii de performanţă, vor propaga efecte pozitive
în toate fazele realizării codului sursă şi ale utilizării software-ului la beneficiari. Se
681

pune în evidenţă legătura dintre caracteristicile de ordin cantitativ şi laturile


calitative ale unul limbaj de asamblare şi efectele de antrenare multiplă induse.

21.2 Structura instrucţiunii limbajului de asamblare

Programatorul în limbajele de asamblare, înainte de orice, trebuie să cu-


noască structura internă a instrucţiunilor, care diferă de la un limbaj la altul.
Structura internă arată modul în care se dispun la nivel de biţi informaţiile privind:
codul operaţiilor, etichetele şi expresiile de adresare ale operanzilor.
Şirul de biţi care memorează codul operaţiei are o lungime strict dependentă
de numărul de operaţii de bază pe care le implementează limbajul de asamblare.
Astfel, dacă proiectantul limbajului de asamblare optează pentru un şir de
opt biţi, limbajul de asamblare va fi înzestrat cu maxim 256 de mnemonice asociate
unor instrucţiuni diferite. Numărul foarte mare de mnemonice disponibile permite
definiri de instrucţiuni diferite pentru aceleaşi operaţii în cazul în care tipul
operanzilor este altul. De exemplu, vor exista instrucţiuni distincte pentru
efectuarea adunării în binar, în zecimal împachetat şi în virgulă mobilă.
Programatorul ce utilizează limbajul de asamblare va alege aritmetica în care
lucrează strict dependent de contextul problemei pe care o rezolvă.
Dacă proiectantul limbajului de asamblare defineşte codul operaţiei ca un
şir de şapte biţi, numărul mnemonicelor cu care va manipula programatorul va fi de
cel mult 127. Un astfel de limbaj este mai sărac, posibilităţile de alegere se reduc.
Chiar dacă pot fi definiţi operanzi de tipuri diferite, implementarea aritmeticilor
presupune apelarea funcţiilor care să prelucreze datele fiecărui tip folosind operaţii
dintre cele 127 implementate în limbaj. Efortul de programare este mult mai
ridicat.
Este posibil ca numărul de instrucţiuni N, să fie mai mic decât 2k unde k
reprezintă lungimea şirului de biţi pe care este memorat codul instrucţiunilor.
Gradul de ocupare, G este dat de relaţia:

N1
G= *100
2k

Gradul de neocupare Γ este dat de relaţia:

Γ = 100 – G
682

Gradul de neocupare este cel care oferă posibilitatea designerilor să


dezvolte un sistem de macrodefiniţii coerent sau să impună definirea de noi operaţii
de bază ce conduc la reducerea efortului de programare.
Designul structural al limbajului de asamblare are la bază ipoteza conform
căreia toate elementele limbajului au aceeaşi importanţă. Această viziune conduce
la abordarea separată a componentelor instrucţiunii.
În designul structural resursele sunt privite de sine stătător. Operanzii sunt
stocaţi în registre (R), în memorie (M) sau sunt definiţi în corpul instrucţiunii,
imediat (I). Complexitatea limbajului de asamblare se modifică radical în cazul
opţiunilor designerului pentru instrucţiunile cu doi operanzi.
Dacă se optează pentru întreaga gamă de structuri de operanzi, vor fi
definite instrucţiuni de tip R-R, R-M, R-I, M-M, M-I. Structurile interne ale acestor
tipuri vor ocupa zone de memorie de lungime variabilă.
În cazul în care există 16 registre de lucru codul operaţiei ocupă 8 biţi, o
instrucţiune de tip R-R va fi definită pe cel puţin 16 biţi. Pentru operaţii R-R se
asociază coduri distincte.
În cazul în care codul operaţiei este unic, nedepinzând de operanzi, sunt
necesari încă 3 biţi, pentru a indica tipul (000 pentru tipul R-R, 001 pentru tipul R-
M, 010 pentru tipul R-I etc.). Limbajele de asamblare ale generaţiilor de
microprocesoare actuale evită tipul de instrucţiuni M-M, având definite mnemonice
pentru lucrul cu şiruri de caractere, suficient de flexibile. Expresiile de adresare se
evaluează în timpul asamblării, semnificaţiile fiind date de coduri memorate pe
anumite poziţii din corpul structurii interne a instrucţiunii.
Folosind o zonă de 2 biţi se obţin toate combinaţiile ce corespund
atributelor direct / indirect şi indexat / neindexat. Pentru tipologii identificate şi
limitate ca număr de expresii de adresare se stabileşte o zonă care memorează
coduri asociate care prin interpretare în timpul execuţiei permit utilizarea corectă a
informaţiilor din zonele "operanzi" din structura internă a instrucţiunii. Codurile
conduc la posibilitatea realizării unei variabilităţi a lungimii zonei de memorie
ocupată de informaţiile despre operanzi.
Designul limbajului de asamblare pentru microprocesoarele 80x86 a condus
la o structură de instrucţiune internă specifică tipurilor de instrucţiuni R-R, R-M,
R-I, codul operaţiei are încorporate informaţii privind formatul datelor (bait,
cuvânt, cuvânt dublu) şi direcţia de parcurgere (adresare sau traversare) a
memoriei, figura 21.1.

T T T T T T d w M M reg r/m
Biţii 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0

Figura 21.1 – Structura internă a instrucţiunii


unde:
T - biţi pentru codul operaţiei;
683

d - direcţie de parcurgere;
w - tip operand (bait/cuvânt);
MM - interpretarea deplasării;
reg - registre (semiregistre) codificate;
r/m - tipologii expresii de adresare.
Limbajele de asamblare mai vechi, datorită numărului mare de registre şi a
aritmeticilor implementate, au regrupat expresiile de adresare pe un număr mai
restrâns de biţi. Gradul de dependenţă a câmpurilor din structura instrucţiunilor era
foarte slabă.
Limbajul de asamblare a microprocesorului convenţional x86 are definit un
grad de dependentă ridicat între elementele din structura instrucţiunii. Astfel,
deplasarea este interpretată funcţie de câmpul w (câmpul MM depinde de câmpul
w), iar câmpul reg depinde ca interpretare tot de câmpul w. Câmpul r/m depinde
de câmpul MM. Dacă valoarea MM este 11, câmpul r/m este interpretat reg.

21.3 Designul corectiv pentru limbajele de asamblare


Pentru limbaje precum Fortran, Cobol, PL/l în timp au fost efectuate analize
statistice, toate evidenţiind neuniformitatea cu care programatorii utilizează
instrucţiunile şi tipurile de date şi de structuri definite.
În mod normal, experienţa oferită de utilizarea facilităţilor trebuie să
conducă la perfecţionarea limbajelor, aspect realizat prin trecerea de la FORTRAN
IV Ia FORTRAN ‘77 şi acum deja există FORTRAN ‘95. Mai dinamic, limbajul C
a înregistrat evoluţiile C++ şi VISUAL C++. Limbajele de asamblare sunt cele
chemate să permită implementarea noilor mecanisme. Avantajele lucrului pe stivă a
condus la implementarea de instrucţiuni (push, pop) absente la primele limbaje.
Frecvenţa redusă de lucru în aritmetica zecimal împachetată a condus la excluderea
din lista mnemonicelor a elementelor corespunzătoare operaţiilor acestei aritmetici.
Se presupune existenţa unui program, simplu de altfel, care citeşte fişiere cu
texte sursă în limbaj de asamblare şi contorizează. instrucţiunile. Se observă clar că
frecvenţa cu care apar instrucţiuni precum mov, add, inc, dec diferă radical de
frecvenţa cu care apar instrucţiuni ca hlt, aaa, aad, das, lock, les, xchg.
De asemenea, dacă alături de frecvenţele cu care sunt utilizate resursele (registre,
zone de memorie) se vor putea defini asocieri operaţii - resurse care trebuie tratate
distinct.
Existenţa metodelor statistice moderne oferă un instrument eficient de
grupare a instrucţiunilor legate de operanzi ca un tot si tratarea distinctă a
instrucţiunilor din punct de vedere al utilizării. Toate clasificările instrucţiunilor
(după tipul operaţiei, natura operanzilor, tipul operanzilor) sunt strict legate de
latura semantică a limbajului. Metodele de clasificare statistice oferă posibilitatea
de a structura limbajul de asamblare după rigorile utilizatorilor.
684

Se consideră o matrice a frecventelor F cu elemente fij care au semnificaţia:


număr de apariţii ale instrucţiunii Ai cu utilizarea resursei Sj. Prin definirea unui
sistem de ponderi adecvat, se procedează la agregarea informaţiilor, obţinându-se o
măsură a intensităţii întrebuinţării unei instrucţiuni într-un context dat (operatori
utilizaţi). Un algoritm de clasificare conduce la identificarea unei noi tipologii de
instrucţiuni. Structura internă a instrucţiunii este rezultatul natural al utilizării. Se
porneşte la proiectarea limbajului de asamblare de la cerinţele reale ale
programatorilor. Se spune că se utilizează un design corectiv, întrucât clasele se
obţin pornind de la programe scrise într-un limbaj existent. Noul limbaj de
asamblare este rezultatul introducerii de corecţii la un limbaj existent. Pentru ca
rezultatele să fie semnificative este necesar ca eşantionul de programe cu care s-a
efectuat constituirea matricei F să fie suficient de mare şi să cuprindă o diversitate
de programe care să acopere multitudinea de aplicaţii care se dezvoltă în limbaje de
asamblare.
Clasele de instrucţiuni obţinute după frecvenţele de întrebuinţare în
programe vor avea asociate coduri ale operaţiilor care din structură să permită
interpretări diferite.
Dacă de exemplu, instrucţiunea inc ax are frecvenţa de apariţie cea mai
mare, i se va asocia codul de operaţie format numai din zerouri.
Dacă se optează pentru un limbaj de asamblare cu codul operaţiei format
din 8 biţi, zerourile nesemnificative ca număr permit crearea de grupe de
instrucţiuni, fără o acoperire consecutivă a submulţimilor formate din codurile
consecutive.
În ipoteza că în clasa de instrucţiuni foarte frecvent întâlnite se află 18
elemente, reprezentabile pe cinci poziţii binare, codurile acestei clase vor avea trei
zerouri în faţă (000xxxxx). Din cele 32 de combinaţii binare cu cinci poziţii sunt
utilizate numai 18.
Într-o primă etapă se realizează punerea în corespondenţă a claselor cu
codurile. În cazul în care rămân instrucţiuni cărora nu li se asociază coduri datorită
epuizării celor 256 de combinaţii binare, se trece la rafinarea claselor.
Rafinarea este un procedeu complex care vizează fie reducerea numărului
de clase, fie translaţi de instrucţiuni de la o clasă la alta, obţinându-se o altă
repartizare a instrucţiunilor în clase.
Dacă prin restrângerea claselor sau prin translaţie s-a obţinut creşterea
numărului de elemente de la 18 la 31 în clasa instrucţiunilor cel mai frecvent
folosite şi dacă ş-a obţinut încadrarea instrucţiunilor prin punere în corespondenţă
cu coduri binare şi de opt poziţii, se poate trece la dezvoltarea în continuare a
structurii interne a instrucţiunilor limbajului de asamblare.
Un astfel de limbaj prezintă particularitatea că structurarea instrucţiunilor
începe chiar cu codul operaţiei. Este mai corect să se vorbească de codul operaţiei-
operand, întrucât frecvenţele de apariţie ţin seama de operaţie şi de operanzii
utilizaţi.
685

Aparent un astfel de limbaj nu mai prezintă regularitaţile întâlnite la


celelalte limbaje de asamblare.
Structurarea în continuare a instrucţiunilor se efectuează depinzând strict de
alcătuirea claselor. Tipurile de adresare, modul de parcurgere (stânga/ dreapta),
expresiile de adresare, se vor regăsi sau nu la fiecare clasă, după cum rezultă din
elementele care o alcătuiesc. Cifrele semnificative de pe poziţiile zero, unu sau doi
vor marca fiecare tip structural. Chiar dacă se înregistrează "întreruperi" în
secvenţele de atribuire a codurilor, clasele neavând exact 32 de elemente,
codificarea aceasta a instrucţiunilor tine seama de particularităţile de utilizare ale
limbajului.

21.4 Stabilirea numărului de registre


Limbajele de asamblare consideră registrele ca resurse. Se definesc
instrucţiuni în care se utilizează numai anumite registre, ceea ce restricţionează
foarte mult manipularea datelor. Înseamnă că registrele nu au funcţiuni identice,
unele dintre ele fiind dedicate.
Astfel, în cazul limbajului de asamblare definit pentru microprocesoarele
x86, registrul AX este dedicat pentru numeroase operaţii (ajustări, înmulţiri,
împărţiri) şi este destinaţie pentru multe operaţii. Problematica pe care o are
designerul este de a stabili numărul optim de registre pe care să le gestioneze
eficient (statistic) programatorii. Acest optim este rezultatul unui proces de definire
la birou a secvenţelor în diferite ipoteze cu luarea în considerare a numărului de
cicluri pe care le determină fiecare soluţie dată. Se consideră un lot de probleme
frecvent rezolvate in limbaj de asamblare P1, P2, ... Pm. Se vor scrie programe în
ipoteza în care limbajul este definit cu un singur registru de lucru. Se va observa
abundenţa de instrucţiuni mov pentru stocare rezultate intermediare şi pentru
iniţializări. Scriind un program de analiză a celor m programe care oferă soluţii
pentru problemele Pi, i=1,2,..,m, se evaluează volumul de prelucrări exprimat ca
număr de cicluri maşină.
Tot astfel se procedează şi pentru definirile de limbaje de asamblare în care
există două, trei sau mai multe registre. Numărul de registre determină şi
implementările de structuri fundamentale prin intermediul expresiilor de adresare.
De exemplu, registrul index permite atât implementarea structurii de dată masiv,
cât şi definirea structurii repetitive.
Utilizarea indicatorului număr de cicluri maşină, conduce la omogenizarea
de rezultate, chiar dacă se pierd anumite informaţii. El permite agregări şi
manipularea cu medii aritmetice şi dispersii.
Dacă se consideră mai multe loturi de probleme, este posibilă analiza
stabilităţii limbajului de asamblare - tot din punct de vedere statistic. În cazul în
care s-a obţinut un grad de stabilitate corespunzător, se poate trece la alegerea
686

numărului eficient de registre în raport cu criteriul reducerii numărului de cicluri


maşină.
Cu cât lotul problemelor este mai vast, există posibilitatea de a acoperi o
gamă mare de aplicaţii şi de a face un studiu mai complet asupra comportamentului
limbajului de asamblare. Indiferent de mărimea lotului, este riscant să se vorbească
despre optimizarea limbajului sau de stabilirea numărului optim de registre de
lucru, dedicate sau nu. Oricum atributul de optim se va referi la lotul de programe
rezultat, orice extensie fiind riscantă dacă nu s-a studiat suficient reprezentativitatea
acestui lot.
Cercetările efectuate până în prezent pe un lot de 60 de programe cu acelaşi
grad de complexitate pune în evidenţă eficienţa unui limbaj de asamblare cu două
registre acumulator (împerecheate corespunzător pentru a se face identificarea
corectă a operanzilor şi pentru eliminarea ambiguităţii în cazul ajustărilor). De
asemenea, tipurile de expresii de adresă ce intervin în registre pot fi dezvoltate
pentru a introduce nivele de indirectare de ordin superior.

21.5 Concluzii
Designul limbajului de programare cu luarea în considerare a finalităţii,
scrierea de programe, determină proiectarea unui limbaj pentru utilizator.
Numeroase dificultăţi care apar în asamblare, la generarea formei interne a
instrucţiunilor, sunt probleme independente de programator. Odată rezolvate
corect, programul asamblor va opera asupra unei mulţimi de programe în creştere,
propagând efectele pozitive ale limbajului.
Dacă la proiectare sunt luate în considerare şi elemente de compresie a
programelor în cod maşină, limbajul de asamblare oferă o trăsătură benefică tot la
nivelul utilizatorilor finali. Preocupările de design pentru limbaje de asamblare
capătă acum o nouă caracteristică, aceea de a fi deschis spre utilizatori. Opţiunea
spre neomogenitate de tratare a instrucţiunilor nu determină complicaţii la nivelul
utilizatorilor. La nivelul celor care implementează limbajul de asamblare, fiecare
neomogenitate se traduce în modalitate distinctă de tratare. Diversităţii de tipuri de
instrucţiuni îi va corespunde o creştere a complexităţii programului asamblor.
Pentru a obţine rezultate cu nivel de stabilitate ridicat este necesară crearea
unei baze de programe scrise în limbaj de asamblare care să includă cât mai multe
stiluri de programe şi cât mai multe tipuri de probleme rezolvate.
Ca şi în cazul altor limbaje, designul limbajelor de asamblare ia în
considerare menţinerea unui nivel ridicat al ortogonalităţii instrucţiunilor. Studiul
efectuat acum a presupus ortogonalitatea deja existentă a instrucţiunilor din
limbajul de asamblare asociat microprocesoarelor x86, fără a se proceda la
definirea de noi instrucţiuni sau noi tipuri de expresii de adresare. Dacă se vor
efectua în viitor şi aceste modificări, designul limbajului de asamblare capătă un
687

nivel de profunzime mult mai accentuat, influenţând portabilitatea limbajului, în


sensul reducerii efortului de integrare a componentelor în software neomogen.
688

22
ELEMENTE DE GRAFICĂ

22.1 Istoric al adaptoarelor grafice

Imaginea pe care un computer o reprezintă cu ajutorul unui monitor a fost


încă de la începuturile erei informatice cea mai importantă metodă utilizată pentru
a interacţiona cu utilizatorul. Ca multe alte familii de calculatoare, cele compatibile
IBM-PC folosesc în acest scop un dispozitiv electronic numit adaptor grafic sau
placă video şi dispozitivul de afişare propriu-zis (monitorul, ecranul cu cristale
lichide şi altele).
Sistemele pentru afişarea informaţiei s-au dezvoltat foarte mult începând cu
monitoarele monocrome folosite pentru procesarea de text şi sistemele bazate
exclusiv pe modul text din jurul anilor 1970.
În anul 1981 compania IBM a introdus adaptorul grafic color CGA (Color
Graphics Adapter). Acest sistem era capabil să reprezinte patru culori şi avea o
rezoluţie de 320x200. Deşi adaptoarele CGA ofereau facilităţi pentru grafică,
aceste facilităţi erau mult prea simple comparativ cu nivelul cerinţelor pentru
procesare de text, procesare grafică sau alte aplicaţii grafice sofisticate.
În anul 1984 compania IBM a introdus adaptorul grafic EGA (Enhanced
Graphics Adapter). Acest adaptor, cu performanţe mult mai bune decât
precedentul, oferea o rezoluţie mult îmbunătăţită de 640x350 de pixeli şi
posibilitatea de a reprezenta 16 culori simultan.
În 1987 compania IBM a făcut public ceea ce urma să devină standardul
minim acceptat, păstrat şi astăzi, şi anume adaptorul VGA (Video Graphics
Adapter). Rezoluţia maximă a acestui adaptor depinde de numărul de culori
afişabile simultan. Astfel, se poate opta între 640x480 de pixeli cu 16 culori
simultane sau 320x200 de pixeli cu 256 de culori simultane. Toate calculatoarele
actuale compatibile IBM-PC au un adaptor grafic compatibil VGA.
În 1990 compania IBM a introdus adaptorul grafic extins XGA (Extended
Graphics Array), ca succesor al sistemului 8514/A, propriu IBM. O versiune
ulterioară a acestui adaptor, XGA-2, oferea o rezoluţie de 800x600 pixeli în mod
„true color”, aproximativ 16 milioane de culori, sau 1024x768 de pixeli în mod
„high color”, adică 65536 de culori afişabile simultan.
Cele mai multe adaptoare grafice vândute în ziua de azi sunt descrise ca
SVGA (Super Video Graphics Array). Iniţial SVGA a însemnat doar „ceva mai
bun decât VGA”, diverşi producători încercând să impună propriul standard.
689

Ulterior, VESA (Video Electronics Standards Association) a stabilit un standard


comun pentru adaptoarele SVGA, numit „Extensia BIOS VESA”.
În mod obişnuit un adaptor grafic SVGA suportă o paletă de 16000000
culori, deşi cantitatea de memorie video limiteză numărul de culori în anumite
rezoluţii. În general, cu cât diagonala monitorului este mai mare cu atât el poate
reprezenta rezoluţii mai mari. Astfel, un monitor SVGA cu diagonala de 14 inch de
obicei poate afişa 800x600 pixeli, pe când un monitor mare, cu diagonala de 21 de
inch reprezintă 1280x1024 sau chiar 1600x1200 pixeli.
O imagine reprezentată pe monitor are trei caracteristici principale:
 rezoluţia, aceasta fiind produsul dintre numărul de pixeli reprezentaţi pe
orizontală şi verticală (de exemplu 320x200, 640x480)
 numărul de culori afişabile simultan; din acest punct de vedere modurile
grafice sunt paletate sau nepaletate.
 rata de reîmprospătare a imaginii (sau refresh), care poate varia între
limite largi; VESA recomandă o rată de 72Hz (72 cadre/secundă).
Un mod grafic este definit ca o combinaţie între rezoluţia afişată şi numărul
de culori afişabile. Un caz particular al modurilor grafice, prezent numai la
calculatoarele IBM-PC, este aşa-numitul „mod text”. Prin acest mod se reprezintă
caractere pe ecran prin scriere direct în memoria principală la adresa B800:0000h,
rutinele BIOS ale calculatorului făcând scanarea zonei respective şi conversiile
necesare afişării caracterelor pe ecran, folosind matriţe de fonturi.
S-a menţionat anterior că modurile grafice sunt paletate sau nu. Un mod
grafic paletat are asociat o paletă de culori, iar valorile folosite pentru a stabili
culoarea unui pixel pe ecran nu reprezintă culori absolute, ci indecşi în paleta de
culori, care conţine culori în format RGB. Într-un mod grafic nepaletat fiecare pixel
are asociat un grup de baiţi care conţin culoarea reală. Astfel, există următoarele
moduri nepaletate:
 pixel reprezentat pe 2 baiţi, cu 32768 de culori (5 biţi pentru fiecare
componentă RGB)
 pixel reprezentat pe 2 baiţi, cu 65536 de culori (5 biţi pentru roşu şi
albastru, şi 6 biţi pentru verde), mod numit high color
 pixel reprezentat pe 3 baiţi, cu 16777216 de culori (un bait pentru
fiecare componentă RGB), mod numit true color
 pixel reprezentat pe 4 baiţi – mod true color cu informaţie de
transparenţă (un bait pentru fiecare componentă RGB şi un bait pentru
canalul de transparenţă, numit alpha channel)
Producerea imaginii pe ecranul monitorului se face prin operaţia de scanare
a memoriei video. Astfel, informaţia din memoria video este procesată de un
convertor analogic-digital (DAC), care transformă imaginea înscrisă în mod liniar
din memoria video în semnal analogic care este transmis mai departe monitorului.
Numărul de culori afişabil simultan depinde de capacitatea de stocare
alocată fiecărui pixel. Astfel, 4 biţi permit 16 culori simultane, iar 3 baiţi permit
690

aproximativ 16000000 de culori. Generalizând, cu ajutorul a n biţi se reprezintă 2 n


culori.
Regula principală pentru determinarea numărului maxim de culori afişabile
la o anumită rezoluţie este următoarea:

rezoluţie orizontală * rezoluţie verticală * memorie alocată pentru un pixel <= memoria video

Astfel, un adaptor grafic ce dispune de 4Mbytes memorie video poate


reprezenta 1280x1024 cu maxim 65536 de culori sau 1024x768 cu maxim
16000000 milioane de culori.

22.2 Rutine BIOS şi moduri grafice

Rutinele BIOS furnizează un mod elegant de a programa


adaptoarele video. Folosind întreruperea 10h se poate invoca un
set de funcţii care permit stabilirea modului video, citi/scrie
valorile pentru pixeli. În tabelul de mai jos sunt listate modurile
video valabile în rutinele video BIOS atât în mod text cât şi
grafic, fiecare specifice unui anumit tip de monitor (dacă
adaptorul acceptă mai multe tipuri de monitor). BIOS-ul este
folosit pentru operaţii primare de afişare. Pentru animaţie mai
complicată şi mai rapidă este recomandată citirea/scrierea
directă în memoria video.

Tabelul 22.1. Modurile video BIOS


Modul Rezoluţie şi culori Tipul de adaptor
0 40X25 text în 16 nuanţe de gri CGA
1 40X25 text în 16 sau 8 culori CGA
2 80X25 text în 16 nuanţe de gri CGA
3 80X25 text în 16 sau 8 culori CGA
4 320X200 grafic în 4 culori CGA
5 320X200 grafic în 16 nuanţe de gri CGA
6 640X200 grafic în alb şi negru CGA
7 80X25 text în alb şi negru Monochrom
13 320X200 grafic în 16 culori EGA
14 640X200 grafic în 16 culori EGA
15 640X350 grafic în alb şi negru EGA
16 640X350 grafic în 4 sau 16 culori EGA
17 640X480 grafic în alb şi negru VGA
18 640X480 grafic în 16 culori VGA
691

19 320X200 grafic în 256 culori VGA

Dacă se dezvoltă o aplicaţie, mai întâi trebuie determinat tipul adaptorului


grafic şi ales cel mai bun mod video pe care hardware-ul îl poate suporta.
Procedura “DetVideoAdap” face acest lucru şi îl întoarce în registrul AX. Folosind
această informaţie, se alege cel mai bun mod video din tabelul 22.1 care se
potriveşte aplicaţiei. De exemplu, dacă se găseşte VGA, se poate alege modul 8,
care va permite afişarea în 16 culori folosind rezoluţie de 640X480. După ce se
alege modul video, se va folosi funcţia BIOS de setare a modului video după cum
urmează:
; In segmentul de date

modv db 0
.
.
; In segmentul de cod
.
.
mov ah, 0 ; setarea modului video
mov al, modv ; folosind intreruperea 10h
int 10h
.
.

; determina modul video


;inainte de de a apela aceasta procedură, setati ES:DI cu adresa
;unui buffer de 64 octeti
;procedura returnează una din urmatoarele constante în registrul
;AX

MDA = 1;
HGC = 2;
CGA = 3;
EGA = 4;
VGA = 5;

.model SMALL
.code

Wich_adapter PROC
mov ax, 1B00h
int 10h
cmp al, 1Bh ; al va fi 1Bh pentru VGA
jne TestIfEGA
mov ax, VGA
jmp SHORT DoneAdapter
692
TestIfEGA :
mov ax, 40h
mov es, ax
test BYTE PTR es:[87h], 0FFh
jz TestIfCGAorHGC
mov ax, EGA
jmp SHORT DoneAdapter

TestIfCGAorHCG:
; mai intai obtine flag-ul echipamentului
int 11h
and al, 30h ; verifica modul video initial
cmp al, 30h
jne ItIsCGA
mov cx, 800h
mov dx, 3BAh
TestIfHGC:
in al, dx ; in HGC, bit-ul 7 al port-ului 3BAh
test al, 80h ; va fi 1 in timpul retragerii
verticale
jnz ItIsHGC ; ciclează pentru a vedea dacă bitul
devine 1
loop TestIfHGC
;altfel este MDA
mov ax, MDA
jmp SHORT DoneAdapter
ItIsHGC:
mov ax, HGC
jmp SHORT DoneAdapter
ItIsCGA:
mov ax, CGA
DoneAdapter:
ret
WichAdapter ENDP
END

Înainte de a apela procedura “DetVideoAdap” se va iniţializa valoarea


registrului ES:DI cu adresa unui buffer (zone de memorie rezervată) de 64 octeţi.
Dacă este detectat modul video VGA, funcţia 1Bh a întreruperii video BIOS 10h
returnează 64 de octeţi de informaţie in acest buffer.

22.3 Lucrul în mod grafic folosind un adaptor VGA


Modul video 13h, din VGA standard, este modul în care se
realizează cu cea mai mare uşurinţă cele mai rapide rutine de
grafică color.
693

Acest mod foloseşte un singur segment de memorie liniar, mapat pe pixeli


în aşa fel încât fiecare bait controlează exact un pixel. Astfel, sunt posibile 256 de
culori simultan pe ecran, cu o rezoluţie de 320x200 pixeli.
Acest mod este paletat, în sensul că nu se lucrează cu valori absolute de
culoare. Atributul de culoare al fiecărui pixel este un deplasament în interiorul unei
palete care conţine valori RGB reale. Schimbarea valorilor din paleta de culori este
o tehnică de animaţie specifică modurilor grafice paletate, deoarece schimbarea se
reflectă imediat în imaginea prezentată pe ecran.
Folosind modul video VGA 13h se poate afişa o
imagine bitmap de mărime 320x200 cu 256 de culori pe
ecran doar prin copierea acestei imagini la adresa
A000h:0000h. Acest mod de lucru face posibile tehnici de
animaţie foarte rapide cu eliminarea fenomenului de
“flickering” (clipirea ecranului sau ruperea frame-urilor
datorită unui refresh video prea lent).
O astfel de tehnică este următoarea: presupunând că există un mic desen de
25x25 de pixeli care se doreşte a fi mutat în diverse poziţii pe ecran, metoda constă
în punerea unei imagini de fond folosind tehnica prezentată anterior, copierea zonei
de 25x25 pixeli unde se doreşte a fi desenul într-un buffer temporar, plasarea
desenului pe ecran în poziţia salvată, aşteptarea unui anumit timp dictat de viteza
sistemului şi a animaţiei dorite urmată de rescrierea bufferului salvat în vechea
poziţie după care se trece la o nouă poziţie. Folosind această metodă nu se modifică
în nici un fel imaginea de fond, iar calitatea animaţiei este foarte bună.
Printre modurile VGA există un mod nedocumentat, numit “modul X”, care
este foarte asemănător cu modul 13h cu excepţia faptului că dispune de mai mulţi
pixeli pe verticală (320x240), la 256 de culori. Adresarea acestui mod presupune
folosirea regiştrilor VGA, nemaifiind posibilă adresarea directă prin scriere la
adresa A000h:0000h.
De asemenea se poate programa in VGA folosind o rezoluţie de 320x400 cu
256 de culori. Acest mod este tot modul 13h dar se folosesc 4 plane de culoare.
Modul 13h are această rezoluţie în mod nativ, dar designerii au dorit să folosească
numai 64k din motive de segmentare a memoriei în modul de adresare real, şi astfel
fiecare linie este afişată de două ori, de unde rezoluţia verticală înjumătăţită
(320x400). Dacă se folosesc 4 plane de biţi se poate controla fiecare a doua linie
ascunsă.
694

Figura 22.1 – Iluzie optică

Se prezintă în continuare un program care realizează o iluzie optică,


folosind modul VGA 13h, 320x200 cu 256 de culori, figura 22.1.
Bara din centrul imaginii este realizată folosind o singura culoare, dar pare
construită dintr-un gradient datorită imaginii de fond.
; Acest cod a fost asamblat cu NBASM 00.23.xx
.model tiny
.code

org 100h ; adresa de inceput al unui .COM

.start

push ds ; asigură ds=es


pop es

mov cx,64 ; setare paletă de culori cu


; 256 nuanţe de gri incepînd
xor ax,ax ; cu 0.0.0, 1.1.1, 2,2,2, ...
mov di,offset Palette ;
PLoop: stosb ;
stosb ;
stosb ;
inc ax ;
loop PLoop ;

mov ax,0013h; setăm modul video la


; rezoluţia 320x200x256
int 10h ;

mov dx,offset Palette ; scriem paleta în DAC


xor bx,bx ;
695
mov cx,64 ;
mov ax,1012h ;
int 10h ;

mov ax,0A000h ; pointer la mamoria VGA


mov es,ax ;

mov di,14464 ; plasăm imaginea în centrul


; ecranului

call Fade ; afişăm partea superioară

mov cx,10 ; afişăm bara din mijloc (10


; linii)
mov al,32 ; culoarea din mijloc
ALoop: push cx ;
mov cx,128 ;
rep ;
stosb ;
add di,192 ;
pop cx ;
loop ALoop ;

call Fade ; afişăm a treia parte

xor ah,ah ; aşteptăm o tastă


int 16h

mov ax,0003h ; setăm modul text(80x25)


int 10h ;

.exit ; ieşire în DOS

Fade proc near ; afişarăm prima şi a treia


; parte
mov cx,50 ; 50 de linii
PLoop1: push cx ;
mov cx,64 ; 64 de culori
PLoop2: mov al,cl ;
dec al ;
stosb ; câte două coloane pentru
; fiecare culoare
stosb ;
loop PLoop2 ;
add di,192 ;
pop cx ;
loop PLoop1 ;
ret ;
Fade endp ;

Palette dup 768,? ; paleta de culori


696
.end

În continuare este prezentată o colecţie de proceduri sunt folosite pentru


manipularea paletei de culori VGA. Sursa poate fi folosită numai cu
DOS-extender-ul DOS4GW şi trebuie asamblată folosind Turbo
Assembler.
; Manipularea paletei de culori VGA
; se foloseşte modul protejat (numai pentru procesoare >=80386)

ideal ; modul TASM “ideal”


p386 ; procesor 80386
radix 10 ; folosim numere zecimale
locals @@ ; etichete locale @@
model flat ; mod protejat liniar pe 32 de biţi

dataseg

; tabela de indecşi VGA (mod cu 16 culori)

VGA DB 0,1,2,3,4,5,20,7,56,57,58,59,60,61,62,63

; valori RGB implicite (16 culori)

VGACOLOR DB 0, 0, 0, 0, 0,42, 0,42, 0, 0,42,42


DB 42, 0, 0, 42, 0,42, 42,42, 0, 42,42,42
DB 21,21,21, 21,21,63, 21,63,21, 21,63,63
DB 63,21,21, 63,21,63, 63,63,21, 63,63,63
codeseg

PUBLIC SETRGB16, GETRGB16, SETDAC16, GETDAC16


PUBLIC RestoreVGA
PUBLIC SETRGB256, GETRGB256, SETDAC256, GETDAC256

; aşteaptă sincronizarea verticală

proc VerticalSync near


push EAX EDX
mov DX,03DAH ; registru de stare
mov AH,8 ; bit de sincronizare verticală
@@1: in AL,DX ; citeşte starea
test AL,AH ; aşteaptă sincronizarea
jnz @@1
@@2: in AL,DX ; citeşte starea
test AL,AH
jz @@2
pop EDX EAX
ret
endp VerticalSync
697

; aşteaptă sincronizarea orizontală

proc HorizontalSync near


push EAX EDX
mov DX,03DAH ; registru de stare
mov AH,1 ; bit de sincronizare orizontală
@@1: in AL,DX ; citeşte starea
test AL,AH ; aşteaptă sincronizarea
jnz @@1
@@2: in AL,DX ; citeşte starea
test AL,AH
jz @@2
pop EDX EAX
ret
endp HorizontalSync

; setează paleta RGB pentru o anumită culoare (mod cu 16 culori)


; parametri [ESI=culoare EAX=roşu EBX=verde ECX=albastru]
; modifică [EDX]

proc SETRGB16 near


mov AH,AL
add ESI,OFFSET EGA
mov AL,[ESI] ; tabela de culori VGA
mov DX,03C8h ; adresa registrului PEL VGA
out DX,AL
inc DX ; 03C9h = registru de date VGA PEL
mov AL,AH
out DX,AL ; scrie valoare roşu
mov AL,BL
out DX,AL ; scrie valoare verde
mov AL,CL
out DX,AL ; scrie valoare albastru
ret
endp SETRGB16

; regăseşte paleta RGB pentru o anumită culoare (mod cu 16 culori)


; parametri [ESI=culoare EAX=roşu EBX=verde ECX=albastru]
; modifică [EDX]

proc GETRGB16 near


and EAX,0Fh
add EAX,OFFSET EGA
mov AL,[EAX] ; tabela de culori VGA
mov DX,03C7h ; adresa registrului PEL VGA
out DX,AL
add DX,2 ; 03C9h = registru de date VGA PEL
in AL,DX ; citeşte valoare roşu
mov [EBX],AL
in AL,DX ; citeşte valoare verde
mov [ESI],AL
698
in AL,DX ; citeşte valoare verde
mov [EDI],AL
ret
endp GETRGB16

; setează valori RGB pentru un bloc consecutiv de culori (mod cu 16


; culori)
; parametri [EBX=culoare ECX=număr ESI=&sursa]
; modifică [EAX EDX]

proc SetDAC16 near


add EBX,OFFSET VGA
call VerticalSync
cli ; deactivează întreruperile
@@1: mov DX,03C8h ; 03C8h = adresa registrului VGA PEL
mov AL,[EBX] ; tabela de culori VGA
inc EBX
out DX,AL
inc DX ; 03C9h = registru de date VGA PEL
outsb ; scrie valoare roşu
outsb ; scrie valoare verde
outsb ; scrie valoare albastru
dec ECX
jnz @@1
sti ; activează întreruperile
ret
endp SetDAC16

; regăseşte valori RGB pentru un bloc consecutiv de culori (mod cu


16
; culori)
; parametri [EBX=culoare ECX=număr ESI=&sursa]
; modifică [EAX EDX]

proc GetDAC16 near


add EBX,OFFSET VGA
call VerticalSync
cli ; dezactivează întreruperi
@@1: mov DX,03C7h
mov AL,[EBX] ; tabela de culori EGA
inc EBX
out DX,AL ; adresa registrului de scriere
add DX,2 ; 03C9h = registru de date VGA PEL
insb ; citeşte valoare roşu
insb ; citeşte valoare verde
insb ; citeşte valoare albastru
dec ECX
jnz @@1
sti ; activează întreruperi
ret
endp GetDAC16
699
; setează valori implicite RGB (mod cu 16 culori)
; modifică [EAX EBX ECX EDX ESI]

proc RestoreVGA near


mov EBX,0 ; index de start
mov ECX,16 ; număr de culori
mov ESI,OFFSET EGACOLOR ; tabela de culori implicită
call SetDAC16 ; setează paletă culori
ret
endp RestoreVGA

; setează valorile RGB pentru o anumită culoare (mod cu 256 de


culori)
; parametri [EAX=culoare EBX=roşu ECX=verde EDI=albastru]
; modifică [EDX]

proc SETRGB256 near


mov DX,03C8h ; adresă registru scriere VGA PEL
out DX,AL
inc DX ; 03C9h = registru de date VGA PEL
mov AL,BL
out DX,AL ; scrie valoare roşu
mov AL,CL
out DX,AL ; scrie valoare verde
mov AX,DI
out DX,AL ; scrie valoare albastru
ret
endp SETRGB256

; regăseşte valorile RGB pentru o anumită culoare (mod cu 256 de


; culori)
; parametri [EAX=culoare EBX=roşu ECX=verde EDI=albastru]
; modifică [EDX]

proc GETRGB256 near


mov DX,03C7h
out DX,AL
add DX,2 ; 03C9h = registru de date VGA PEL
in AL,DX ; citeşte valoare roşu
mov [EBX],AL
in AL,DX ; citeşte valoare verde
mov [ESI],AL
in AL,DX ; citeşte valoare albastru
mov [EDI],AL
ret
endp GETRGB256

; setează valorile RGB pentru un bloc consecutiv de culori (mod cu


256
; de culori)
; parametri [EAX=culoare ECX=număr de culori ESI=&sursa]
; modifică [EBX EDX]
700

proc SetDAC256 near


mov BX,CX
shl CX,1
add CX,BX
call VerticalSync
cli ; dezactivează întreruperi
mov DX,03C8h ; 03C8h = adresă registru scriere VGA
PEL
out DX,AL
inc DX ; 03C9h = registru de date VGA PEL
rep outsb
sti ; activează întreruperi
ret
endp SetDAC256

; setează valorile RGB pentru un bloc consecutiv de culori (mod cu


256
; de culori)
; parametri [EAX=culoare ECX=număr de culori ESI=&sursa]
; modifică [EBX EDX]

proc GetDAC256 near


mov BX,CX
shl CX,1
add CX,BX
call VerticalSync
cli ; dezactivează întreruperi
mov DX,03C7h
out DX,AL
inc DX ; 03C9h = registru de date VGA PEL
rep insb
sti ; activează întreruperi
ret
endp GetDAC256

end

22.4 Lucrul în mod grafic folosind un adaptor SVGA

Modurile SVGA au fost standardizate de asociaţia producătorilor de


adaptoare video VESA. Există în acest sens două standarde larg implementate în
marea majoritate a plăcilor video, şi anume:
 extensia BIOS VESA 1.2
 extensia BIOS VESA 2.0
Foarte recent VESA a elaborat standardul 3.0, ale cărui specificaţii pot fi
regăsite la adresa http://www.vesa.org.
Extensia BIOS VESA 1.2 oferă în comparaţie cu VGA o mulţime de noi
moduri video, în general cu rezoluţii mari şi moduri de culoare high color - true
701

color. Adresarea memoriei video se face printr-o operaţie de paginare. Astfel,


porţiuni de 64kbytes din memoria video sunt aduse in segmentul A000h-AFFFh,
modificate pentru a se afişa secţiunea de ecran corespunzătoare şi ulterior rescrise
în memoria video. Astfel, este posibilă utilizarea VBE1.2 (VESA BIOS Extension)
folosind modul real de adresare specific 8086.
Extensia BIOS VESA 2.0 este destinată exclusiv modului de lucru protejat,
pentru procesoare din familia 80386, iar adresarea memoriei video se poate face
direct, în mod liniar. Astfel, standardul prevede o rutină BIOS cu care se poate
obţine adresa fictivă (de obicei peste 2Gbytes) la care memoria video este mapată
peste memoria reală adresabilă în mod protejat.
Această secţiune prezintă modul de lucru folosind standardul VESA 1.2.
Codul inclus conţine o procedură de desenare a unei linii, fiind bazat pe algoritmul
lui Bresenham.
Lucrul cu VBE 1.2 presupune următoarele:
 memoria video – de obicei peste 1Mbyte - nu poate fi accesată integral
 memoria video se partajează in secţiuni cu o anumită granularitate
 pentru desenare pe ecran fiecare secţiune trebuie adusă la adresa A000h,
modificată conform algoritmului folosit, şi transferată în memoria video
De exemplu, partajarea unei memorii video de 1Mbyte cu o granularitate de
64kbytes se face în exact 16 secţiuni care sunt aduse la adresa A000h.
Dacă se partajează aceeaşi memorie cu o granularitate de 4 kbytes se obţin mai
multe mapări posibile. Astfel, fereastra adresabilă este tot de 64 kbytes dar această
fereastră poate fi aliniată din 4 în 4 kbytes.
O granularitate mai fină măreşte viteza de afişare. Se foloseşte exemplul de
mai jos pentru a demonstra acest fapt.
Folosind secţiuni de 64kbytes, cuvântul semnificativ din adresa liniară a
pixelului este localizarea secţiunii, iar cuvântul mai puţin semnificativ este poziţia
în cadrul secţiunii.
Folosind secţiuni de 4kbytes, cei mai nesemnificativi 12 biţi sunt
deplasamentul, iar biţii semnificativi care rămân sunt poziţia secţiunii în memoria
video.
Un avantaj important al granularităţii de 4kbytes este faptul că secţiunile se
pot alinia mult mai uşor. Cu o rezoluţie orizontală de 640 de pixeli, 32 de linii
folosesc 20 kbytes, care se divide la 4kbytes. Dacă pentru afişarea unei linii
algoritmul se limitează la ferestre de 20kbytes atunci nu trebuie testată depăşirea
ferestrei.
O granularitate de 64kbytes se aliniază cu ecranul doar la 512 linii, ceea ce
înseamnă 320kbytes, deci trebuie testate depăşirile de fereastră pentru fiecare linie
de rastru orizontală.
Se schimbă, folosind rutine VESA 1.2, lungimea liniei orizontale de rastru
la o mărime care este putere a lui 2 (de exemplu 1024). O astfel de lungime
garantează că o secţiune se va termina întotdeauna la marginea ecranului, dar dacă
702

rezoluţia orizontală este mai mică de 1024 se va pierde foarte multă memorie video
în acest mod.
Procedura de trasare a unei linii prezentată mai jos foloseşte, ca optimizare,
testarea faptului că ambele puncte extreme ale liniei sunt în aceeaşi secţiune. Dacă
ambele puncte sunt în aceeaşi secţiune, atunci nu este necesară schimbarea
secţiunii. Dacă ele nu sunt în aceeaşi secţiune, atunci pentru fiecare punct se
testează depăşirea secţiunii curente.
.486
code segment para public use16
assume cs:code

PgDown macro
push bx
push dx
xor bx,bx
mov dx,cs:winpos
add dx,cs:disp64k
mov cs:winpos,dx
call cs:winfunc
pop dx
pop bx
endm

PgUp macro
push bx
push dx
xor bx,bx
mov dx,cs:winpos
sub dx,1
mov cs:winpos,dx
call cs:winfunc
add di,cs:granmask
inc di
pop dx
pop bx
endm

mov ax,seg stk


mov ss,ax ; setează stiva
mov sp,200h

call GetVESA ; iniţializează variabile VESA

mov ax,4f02h ;
mov bx,0101h ; modul VESA 101h (640x480, 256 culori)
int 10h ;

mov ax,0a000h
mov ds,ax
703

mov eax,10h
mov ebx,13h
mov ecx,20bh ; afişează linie
mov edx,1a1h
mov ebp,21h
call Lin

mov ax,4c00h
int 21h

GetVESA proc
; se iniţializează variabile în funcţie de granularitatea ferestrei
mov ax,4f01h
mov cx,0101h
lea di,buff ; folosim rutina VESA pentru
push cs ; a regăsi parametrii modului 101h
pop es
int 10h
add di,4
mov ax,word ptr es:[di] ;granularitatea ferestrei (în KB)
shl ax,0ah
dec ax
mov cs:granmask,ax ; = granularitatea - 1 (în Bytes)
not ax
clc
GVL1:
inc cs:bitshift
rcl ax,1
jc GVL1
add cs:bitshift,0fh
inc ax
mov disp64k,ax
add di,8
mov eax,dword ptr es:[di] ; adresa ferestri de control
mov cs:winfunc,eax
ret
buff label byte
db 100h dup (?)
endp

Lin proc
; Date de intrare: eax: x1, ebx: y1, cx: x2, dx: y2, bp: culoare
; Modifică: ax, bx, cx, edx, si, edi
; Foloseşte:
;
winfunc(dd),winpos(dw),page(dw),granmask(dw),disp64k(dw),bitshift(d
b)
; eax, ebx trebuie să aibă cuvintele semnificative setate pe 0

cmp dx,bx
ja LinS1 ; sortare vârfuri
704
xchg ax,cx
xchg bx,dx

LinS1:
sub cx,ax
ja LinS2 ; calcul delta_x
neg cx ; modifică bucla internă după semn
xor cs:xinc1[1],28h

LinS2:
sub dx,bx ;delta_y
neg dx
dec dx

shl bx,7
add ax,bx ; calcul adresă liniară de start
lea edi,[eax][ebx*4]

mov si,dx
xor bx,bx
mov ax,cs:page
shl ax,2 ; pageOffset=page*5*disp64K
add ax,cs:page
mul cs:disp64k
push cx ; iniţializează fereastră CPU
mov cl,cs:bitshift ; la punctul superior al liniei
shld edx,edi,cl
pop cx
add dx,ax
and di,cs:granmask
mov cs:winpos,dx
call cs:winfunc
mov dx,si

mov ax,bp
mov bx,dx

;ax:culoare, bx:err-acumulator, cx:deltaX, dx:lungime verticală


;di:localizare în fereastra CPU, si:deltaY, bp:culoare

LinL1:
mov [di],al
add bx,cx
jns LinS3
LinE1:
add di,280h
jc LinR2 ; rutina de desenare
inc dx
jnz LinL1
jmp LinOut
LinL2:
mov [di],al
705
xinc1 label byte
LinS3:
add di,1 ; trecerea la următorul pixel pe
jc LinR1 ; orizontală
LinE2:
add bx,si
jns LinL2
jmp LinE1

LinR1:
js LinS7
PgDown ; mută pagina în jos cu 64k
mov ax,bp
jmp LinE2
LinS7:
PgUp ; sau în sus cu granularitatea
mov ax,bp
jmp LinE2

LinR2:
PgDown
mov ax,bp ; mută pagina în jos cu 64k
inc dx
jnz LinL1

LinOut:
mov cs:xinc1[1],0c7h
ret
endp

winfunc dd ? ; pointer la funcţia VESA setwindow


winpos dw ? ; locaţia ferestrei CPU
granmask dw ? ; masca de adresă in interiorul
ferestrei
disp64k dw ? ;număr de segmente de 64k
page dw 0 ; pagina video (0,1,2 pentru 1MB
video)
bitshift db 0 ; pentru extragerea biţilor de adresă
a ; ferestrei
ends

stk segment para stack use16 'STACK'


dw 100h dup (?)
ends
end

22.5 Concluzii

Acest capitol oferă informaţii introductive şi exemple în domeniul


programării grafice folosind limbajul de asamblare. Trebuie precizat faptul că toate
aceste exemple rulează numai sub sistemul de operare DOS.
706

Programarea grafică in mediul Windows foloseşte în mod obişnuit un strat


de abstractizare care este specific fiecărui adaptor grafic, livrat de producătorul
plăcii sub forma unui driver. La un nivel mai înalt este interfaţa Microsoft DirectX,
care foloseşte numeroasele funcţii de accelerare grafică prezente în plăcile grafice
moderne.
Se remarcă faptul că standardele VESA nu oferă suport pentru accelerare
grafică, fiecare producător de plăci video având propriul set de funcţii API de nivel
scăzut, la marea majoritate nedocumentate.
Resurse suplimentare se regăsesc pe World Wide Web la site-ul VESA,
http://www.vesa.org.

23
PROGRAME REZIDENTE

23.1 Caracteristicile programelor rezidente

Pe microcalculatoarele care folosesc sistemul de operare DOS, la un


moment dat poate rula un singur program. Acesta ocupă memorie RAM şi deţine
controlul sistemului de calcul. La încheierea sa, memoria ocupată este eliberată şi
sistemul de operare DOS preia controlul. Există însă posibilitatea de a realiza
programe care odată încheiate să rămână în memorie, dând totodată posibilitatea
altor programe să ruleze în spaţiul de memorie rămas liber. Ele pot fi activate de
anumite evenimente externe sau interne sau pot fi activate de către un alt program
ce este activ în memorie, în acel moment. Aceste programe se numesc programe
rezidente sau programe TSR (Terminate and Stay Resident).
Bryan Pfaffenberger în „Dicţionar explicativ de calculatoare” defineşte
programul rezident ca fiind „un program accesoriu sau utilitar realizat pentru a
rămâne în memoria cu acces aleatoriu (RAM) în permanenţă, astfel că poate fi
activat cu o comandă, chiar dacă mai este un alt program în memorie”.
Programele rezidente pot fi folosite în operaţii de monitorizare a sistemului.
În acest caz programul poate fi activat la intervale de timp stabilite pentru a verifica
starea sistemului sau este activat de evenimente cum sunt solicitările de resurse,
având astfel posibilitatea de a măsura diferiţi parametri sau de a impune un control
suplimentar la acordarea de resurse aplicaţiei solicitante. În acest sens se
implementează sisteme de securitate care să permită accesul la anumite resurse
doar a persoanelor sau aplicaţiilor autorizate. Un exemplu din această categorie îl
707

reprezintă programele antivirus care instalează un modul rezident ce supraveghează


sistemul şi declanşează alarma la orice activitate suspectă.
O altă categorie de utilizări a programelor rezidente o constituie utilitarele
ce sunt apelate în mijlocul unei aplicaţii prin acţionarea unei combinaţii de taste.
De exemplu, se poate activa un mic program de tip calculator de buzunar ori de
câte ori se doreşte efectuarea unor calcule. Sau se apelează un program de
comunicaţii care să permită transfer de date de la sau către aplicaţia activă. Sau pur
şi simplu se poate apela o bază de date ce conţine informaţii utile pentru
soluţionarea problemelor ivite în timpul rulării unei aplicaţii.
Tot programele rezidente oferă şi posibilitatea de a realiza îmbunătăţiri ale
sistemului de operare. De exemplu se adaugă anumite funcţii care permit
programelor să lucreze cu dispozitive sau cu structuri de date diferite de
standardele DOS. De asemenea se pot adăuga noi comenzi sau mici utilitare celor
oferite de sistemul original de operare, în funcţie de nevoile practice.

23.2 Întreruperi pentru programe rezidente

Întreruperile se clasifică după următoarea schemă:


Mascabile

Externe

Hardware Nemascabile

Interne
Întreruperi BIOS

Sistem

DOS
Software

Utilizator
Figura 23.1 – Clasificarea întreruperilor

Întreruperile hardware sunt cablate în circuitele procesorului, ale plăcii de


bază şi ale perifericelor. Cele externe sunt provocate de diferite echipamente ale
sistemului de calcul, care solicită procesorului tratarea unor evenimente. Ele sunt
de două tipuri:
 mascabile, care pot fi inhibate. Dacă indicatorul de condiţie IF
(Interrupt Flag) al procesorului este 0, atunci procesorul nu mai ia în
considerare solicitarea echipamentului respectiv. Exemple: întreruperi
708

solicitate de tastatură, porturile seriale şi cele paralele, discul hard,


unitatea de floppy disk.
 nemascabile, care nu pot fi inhibate. Procesorul ia întotdeauna în
considerare aceste întreruperi. De regulă, astfel de întreruperi sunt
solicitate când apare o eroare în memoria sistemului (Parity Check
Error).

Întreruperile hardware interne sunt generate de către evenimente interne ale


procesorului, cum ar fi tentativa de a executa o împărţire la 0 sau după execuţia
fiecărei instrucţiuni atunci când indicatorul de condiţie TF (Trap Flag) are valoarea
1.
Întreruperile software sunt generate de programele în execuţie care solicită
procesorului execuţia unei instrucţiuni int n sau into. Aceste întreruperi sunt
asociate unor servicii BIOS (întreruperile software BIOS), unor funcţii DOS
(întreruperile software DOS) sau unor rutine scrise de utilizator (întreruperile
software ale utilizatorului).
Pentru a şti cărei rutine trebuie să predea controlul la producerea unei
întreruperi, sistemul de calcul foloseşte o tabelă numită tabela vectorilor de
întrerupere. Această tabelă începe întotdeauna la adresa absolută 00000h, şi
conţine pentru fiecare dintre cele 256 de întreruperi un pointer de tip FAR către
rutina întreruperii respective, pointer care referă vectorul pointeri ai întreruperii.
Primul pointer FAR conţine adresa rutinei de tratare a întreruperii cu numărul 0, al
doilea pointer conţine adresa rutinei de tratare a întreruperii cu numărul 1, al treilea
pointer conţine adresa rutinei pentru întreruperea cu numărul 2 şi aşa mai departe.

0000:0000h
offset rutină INT 0
segment rutină INT 0 0000:0004h
offset rutină INT 1
segment rutină INT 1 0000:0008h
offset rutină INT 2
segment rutină INT 2 0000:000Ch

0000:03FCh
offset rutină INT 255
segment rutină INT 255 0000:0400h

Figura 23.2 – Tabela vectorului de pointeri ai întreruperilor

Deci adresa pointerului ce conţine adresa rutinei de tratare a întreruperii cu


numărul n este:
709

adresa vector INT n = 4n

La solicitarea unei întreruperi, procesorul execută următoarele operaţii:


 pune în stivă registrele FLAGS, CS şi IP, în această ordine;
 pune pe valoarea 0 indicatorii de condiţie IF şi TF;
 primeşte un întreg pe 8 biţi care reprezintă numărul întreruperii;
 execută un salt la adresa indicată în tabela vectorilor de întreruperi, la
poziţia corespunzătoare numărului întreruperii primit de procesor.
Codul întreruperii este furnizat procesorului în una din următoarele
modalităţi:
 în cazul întreruperilor software numărul este precizat în instrucţiune;
 în cazul întreruperilor hardware interne sau al celor externe nemascabile
numărul este implicit (este cablat în procesor);
 în cazul întreruperilor hardware externe mascabile procesul este
controlat de un circuit special numit PIC (Programable Interrupt
Controller) şi se desfăşoară în felul următor:
o PIC primeşte de la periferice cereri de întrerupere, dintre care o
selectează pe cea de nivel cel mai ridicat şi apoi emite către
procesor o cerere de întrerupere pe linia INTR (Interrupt
Request);
o procesorul verifică starea indicatorului IF;
o dacă IF este 0 atunci cererea este ignorată;
o dacă IF este 1 atunci procesorul emite către PIC un semnal pe
linia INTA (Interrupt Acknowledge);
o la primirea semnalului INTA, PIC transmite pe magistrala de
date numărul întreruperii.
Revenirea din rutina de tratare a întreruperii se face cu o instrucţiune IRET
care încheie rutina de tratare a întreruperii. Această instrucţiune redă controlul la
adresa CS:IP din stivă şi reface indicatorii de condiţie pe baza conţinutului
registrului FLAGS păstrat în stivă. Există însă şi întreruperi care nu refac
indicatorii de condiţie la starea iniţială, deoarece ele comunică programului
întrerupt un rezultat prin intermediul acestor indicatori. O astfel de rutină se încheie
cu o instrucţiune RETF 2.
Pentru întreruperile interne ale procesorului revenirea se face la
instrucţiunea ce a provocat întreruperea. Excepţie face întreruperea provocată de
împărţirea la 0 pentru procesoarele 8086/8088, la care revenirea se face la
instrucţiunea următoare instrucţiunii DIV sau IDIV care a provocat întreruperea. În
cazul celorlalte întreruperi revenirea se face la instrucţiunea următoare.
Orice rutină de tratare a unei întreruperi trebuie să îndeplinească minimum
următoarele condiţii:
 să aibă grijă să păstreze contextul nealterat;
710

 să evite generarea de noi întreruperi;


 să minimizeze timpul de tratare a întreruperii. Acest lucru este necesar
pentru a nu bloca sistemul, deoarece pot exista foarte multe solicitări de
întreruperi care trebuie să fie rezolvate. De asemenea, este de preferat ca
timpul de tratare a unei întreruperi să fie independent de contextul în
care s-a generat întreruperea (în sistemele de execuţie în timp real este
necesar să cunoaştem cu precizie timpul necesar fiecărei rutine);
 înainte de a reveni la programul întrerupt trebuie să asigure scoaterea de
pe stivă a tuturor datelor temporare folosite în tratarea întreruperii
(unele întreruperi lasă pe stive anumite informaţii; dacă a fost apelată o
astfel de întrerupere, trebuie asigurată eliberarea stivei);
 întreruperea nu trebuie să fie apelată din nou în timpul tratării ei.

23.3 Sistemul de operare MS-DOS

Sistemul de operare MS-DOS a fost realizat pentru microcalculatoare de tip


PC, bazate pe procesoarele Intel 80x86. El este un sistem de operare monouser şi
monotasking. Permite rularea unui singur proces la un moment dat, proces ce are
controlul total al sistemului. Procesul activ poate utiliza orice resursă a sistemului
şi poate accesa orice zonă de memorie, inclusiv zona sistem. Până la terminarea sa,
programul activ nu poate fi întrerupt decât de întreruperile hardware. După
încheierea programului activ, sistemul de operare primeşte controlul, putând
încărca şi lansa în execuţie un alt program.
Sistemul de operare MS-DOS asigură următoarele mari categorii de
activităţi:
 controlul încărcării în memorie, al lansării în execuţie şi al terminării
activităţii pentru programele utilizator;
 gestiunea operaţiilor de intrare/ieşire;
 gestiunea colecţiilor de date pe suporţi magnetici.
După cum arată şi numele său (Disk Operating System), MS-DOS este un
sistem de operare stocat pe disc. La pornirea sistemului, el se încarcă de pe disc, se
autoconfigurează şi rămâne rezident în memoria internă până la oprirea sau
resetarea sistemului. Pe disc, sistemul de operare MS-DOS este organizat în trei
fişiere de bază, IO.SYS, MSDOS.SYS şi COMMAND.COM, şi mai multe fişiere
ce conţin comenzi externe ale sistemului (fişiere executabile), drivere suplimentare
sau diverse utilitare.
Componentele de bază ale sistemului de operare MS-DOS sunt
următoarele:
 DOS-BIOS – este memorat pe disc în fişierul IO.SYS (IBMBIO.COM
sau IBMIO.SYS în alte versiuni ale sistemului de operare). Conţine
drivere pentru dispozitivele CON (tastatură şi display), PRN
(imprimantă), AUX (interfaţa serială) şi CLOCK (ceasul de timp real).
711

De asemena, această componentă mai conţine şi drivere pentru discurile


flexibile şi pentru discul hard.
 DOS-Kernel (nucleul DOS) – este memorat pe disc în fişierul
MSDOS.SYS (IBMDOS.COM în alte versiuni). Această componentă
conţine proceduri de tratare a operaţiilor de intrare/ieşire la nivel logic,
precum şi funcţii de control al alocării memoriei, de control al
proceselor şi altele. Toate acestea sunt accesibile prin intermediul
întreruperilor DOS, în special INT 21h care permite accesul la funcţiile
DOS prin punerea în registrul AH a numărului funcţiei, la apelul
întreruperii.
 DOS-Shell (interpretorul de comenzi standard) – este memorat pe
disc în fişierul COMMAND.COM. Interpretorul este cel ce afişează
prompterul DOS la consolă şi acceptă şi execută comenzi de la
tastatură. El cuprinde următoarele părţi:
 rutina de iniţializare, care este încărcată în memorie şi utilizată doar în
procesul de încărcare a sistemului de operare de pe disc;
 partea rezidentă, care conţine rutine de tratare a erorilor critice, a
situaţiei de acţionare a tastelor CTRL-BREAK sau CTRL-C, precum şi
a încheierii programului în execuţie;
 partea tranzitorie, care afişează prompter-ul la consolă, citeşte comanda
de la consolă şi o execută. Această parte conţine comenzile DOS
interne. Când se execută un program utilizator, această parte a
interpretorului de comenzi poate fi suprascrisă. Partea rezidentă
primeşte controlul la încheierea execuţiei programului şi verifică o sumă
de control pentru a determina dacă partea tranzitorie a fost suprascrisă
sau nu. Dacă a fost suprascrisă, atunci partea rezidentă reîncarcă partea
tranzitorie de pe disc.

23.4 Resurse ale programelor rezidente

Organizarea memoriei
După instalarea sistemului de operare MS-DOS, memoria este structurată în
felul următor:
712

0000:0000h
Tabela vectorilor de
întrerupere
Zona de date BIOS 0040:0000h
Nucleul sistemului de
operare
Zona „System Data”
Memoria
convenţională Zona „System Code”
(640ko) Partea rezidentă a
COMMAND.COM

Zona proceselor
tranzitorii
A000:0000h
Memoria video
C000:0000h
Memoria BIOS video
superioară C800:0000h
BIOS periferice
(384ko) F000:0000h
ROM-BIOS
FFFF:000Fh
Figura 23.3 – Organizarea primului megaoctet de memorie internă sub sistemul de
operare MS-DOS

Aceasta este structura pentru primul megaoctet al memoriei interne. El este


urmat de memoria extinsă. Primul megaoctet de memorie împreună cu primul
segment (64ko) din memoria extinsă (numit zona de memorie înaltă) pot fi accesate
direct în modul de lucru real al procesorului. Pentru a accesa restul memoriei
extinse este nevoie de un driver special.
Tabela vectorilor de întrerupere şi zona de date BIOS sunt rezervate încă
înainte de încărcarea sistemului de operare şi încep întotdeauna la locaţii fixe. În
continuarea lor se încarcă nucleul sistemului de operare, format din conţinutul
fişierelor IO.SYS şi MSDOS.SYS (pentru versiunea Microsoft). Urmează zona
System Data care conţine structurile interne ale sistemului de operare: buffere,
stive, tabela fişierelor deschise, drivere, tabelele cu blocurile de control a fişierelor
(FCB), tabela directoarelor curente.
Zona ocupată de sistemul de operare se încheie cu zona System Code şi
partea rezidentă a interpretorului de comenzi COMMAND.COM.
Restul memoriei convenţionale reprezintă memoria aflată la dispoziţia
programelor utilizator. Pentru a putea rula, un program trebuie să încapă în această
zonă de memorie, din care o parte poate să fie rezervată de către programele
rezidente. Dacă are nevoie de mai multă memorie, un program poate folosi
memoria extinsă (dacă este instalat driver-ul corespunzător), dar numai pentru
segmente suplimentare de date. Partea de cod trebuie să se încadreze în zona de
memorie convenţională.
713

Memoriei convenţionale îi urmează un spaţiu de adrese de 384 kiloocteţi ce


formează memoria superioară (Upper Memory). În acest spaţiu de adrese sunt
mapate memoria video, memoriile ROM de pe plăcile de extensie (care conţin
BIOS aferent perifericelor respective: placa video, discul fix sau alte echipamente),
memoria ROM de pe placa de bază (care conţine ROM-BIOS, rutinele POST şi
secvenţa ROM Bootstrap), precum şi alte extensii ROM. Tot aici se mapează
memoria expandată şi, în spaţiul rămas liber, Upper Memory Blocks.
Adresele exacte la care sunt localizate diferitele zone de memorie în cadrul
memoriei superioare diferă de la o generaţie la alta de echipamente, dar limitele
generale sunt cele prezentate în figura de mai sus.

Blocul de control al memoriei

Începând cu locaţia de memorie imediat următoare nucleului sistemului de


operare, orice zonă de memorie este precedată de un bloc de 16 octeţi, numit
Memory Control Block, care oferă informaţii privind zona de memorie ce îi
urmează. Structura unui astfel de bloc este următoarea:

Tabelul 23.1.
Offset Lungime Semnificaţie
0 1 octet identificatorul blocului (M sau Z)
1 1 cuvânt adresa de segment a PSP al
proprietarului
3 1 cuvânt dimensiunea zonei de memorie
asociate (în paragrafe)
5 3 octeţi rezervat
8 8 octeţi numele proprietarului

Identificatorul blocului este întotdeauna caracterul „M”, cu excepţia


ultimului MCB din memoria convenţională, care are caracterul „Z” la
deplasamentul 0. Pentru memoria superioară, blocurile MCB au de asemenea ca
identificator caracterul „M”, cu excepţia ultimului MCB din memorie, care este
identificat prin caracterul „Z”.
La deplasamentul 8 se găseşte numele programului proprietar, doar dacă
zona de memorie rezervată este ocupată de un program. Dacă zona de memorie este
rezervată pentru environment sau pentru date suplimentare, atunci aici nu se va găsi
numele programului.
Se observă că aceste blocuri de tip MCB formează un lanţ: cunoscând
conţinutul unui astfel de bloc se poate determina adresa de segment a următorului
bloc de tip MCB, adunând la adresa de segment a blocului curent, dimensiunea în
paragrafe a zonei de memorie pe care o precede, plus 1:
714

adr_seg_MCBn+1=adr_seg_MCBn+dim+1

unde dim este dimensiunea în paragrafe a zonei de memorie pe care o precede


MCBn .
Adresa de segment a primului MCB din lanţ se află la deplasamentul -2, în
tabela INVARS a sistemului de operare MS-DOS. Adresa acestei tabele se
determină cu ajutorul funcţiei DOS 52h.
Dacă adresa de segment a PSP al proprietarului, care se găseşte în MCB la
deplasamentul 1, este 0, atunci zona de memorie precedată de către acel MCB este
liberă.
Dacă valoarea de la deplasamentul 1 este 8, atunci avem de-a face cu o zonă
sistem. În acest caz, dacă la deplasamentul 8 găsim numele „SD” înseamnă că zona
ce urmează acelui MCB este zona SYSTEM DATA. Dacă aici găsim numele „SC”,
atunci este vorba de zona SYSTEM CODE.
Zona SYSTEM DATA este la rândul ei organizată pe subblocuri de
memorie, fiecare subbloc fiind precedat de către un MCB. Primul MCB de subbloc,
din această zonă se află imediat după blocul MCB ce marchează întreaga zonă
SYSTEM DATA. Subblocurile acestei zone se recunosc după identificatorul al
MCB care le precede (deplasament 0). Acest caracter poate fi:
 „D” – pentru subblocul ce aparţine unui driver încărcat printr-o
comandă DEVICE din CONFIG.SYS. În acest caz, numele de la
deplasamentul 8 este chiar numele driver-ului.
 „F” – pentru subblocul OPEN FILE TABLES;
 „X” – pentru subblocul FILE CONTROL BLOCKS;
 „B” – pentru subblocul BUFFERS;
 „L” – pentru subblocul CURRENT DIRECTORIES;
 „S” – pentru subblocul STACKS.
715

Dacă valoarea aflată la deplasamentul 1 al MCB ce precede o zonă de


memorie este diferită şi de 0 şi de 8, atunci această valoare este adresa de segment
a PSP al programului proprietar. În acest caz, putem avea una din următoarele
subvariante:
 adresa de segment a PSP al proprietarului este egală cu adresa de
segment a MCB curent plus 1. În acest caz, zona de memorie precedată
de MCB curent este ocupată chiar de către programul proprietar, iar la
deplasamentul 8 al MCB se găseşte numele programului.
 valoarea cuvântului de la deplasamentul 1 din MCB curent este egală cu
valoarea cuvântului găsit la deplasamentul 2Ch în PSP al programului
proprietar. În acest caz, zona de memorie precedată de MCB curent este
ocupată de blocul de environment al programului indicat ca proprietar,
prin intermediul PSP propriu.
 dacă valoarea de la deplasamentul 1 din MCB curent nu verifică nici
una din relaţiile de mai sus, atunci este vorba de un bloc de date alocat
suplimentar de către programul indicat ca proprietar.

23.5 Controlul proceselor

Încărcarea în memorie şi lansarea în execuţie a unui program se poate face


de către un alt program prin apelul funcţiei DOS 4Bh (EXEC) sau direct de la
consolă prin introducerea la prompterul interpretorului de comenzi a numelui
programului. Interpretorul de comenzi caută programul specificat în directorul
curent şi, dacă nu îl găseşte acolo, în directoarele conţinute în variabila PATH a
sistemului de operare. Când îl găseşte îl încarcă în memorie şi îl lansează în
execuţie tot prin intermediul funcţiei EXEC.
Programele executabile au extensiile BAT, COM sau EXE. Fişierele cu
extensia BAT sunt de fapt fişiere text ce conţin comenzi DOS. Dacă la prompterul
interpretorului se introduce numele programului fără extensie şi în acelaşi director
se găsesc mai multe fişiere cu numele respectiv, dar cu extensii diferite, atunci
ordinea în care interpretorul de comenzi le ia în considerare este următoarea: mai
întâi extensia COM, apoi cea EXE şi în final extensia BAT.
Programele de tip COM sau EXE sunt precedate în memorie de o zonă de
256 de octeţi numită PSP (Program Segment Prefix). Această zonă de memorie are
următoarea structură:
716

Tabelul 23.2.
Offset Lungime Semnificaţie
00h 1 cuvânt codul unei instrucţiuni INT 20h
02h 1 cuvânt adresa de segment a vârfului
memoriei de bază ocupate
04h 1 octet rezervat
05h 5 octeţi codul unei instrucţiuni CALL
seg_int21h:off_int21h
0Ah 2 cuvinte adresa rutinei de tratare INT 22h
0Eh 2 cuvinte adresa rutinei de tratare INT 23h
12h 2 cuvinte adresa rutinei de tratare INT 24h
16h 1 cuvânt adresa de segment a PSP al
procesului părinte
18h 20 octeţi rezervat
2Ch 1 cuvânt adresa de segment a blocului de
environment
2Eh 46 octeţi rezervat
5Ch 36 octeţi primul FCB standard, nedeschis
6Ch 20 octeţi al doilea FCB standard, nedeschis
80h 1 octet lungimea şirului cu parametrii
programului
81h 127 octeţi parametrii programului din linia de
comandă

Instrucţiunea INT 20h de la deplasamentul 0 reprezintă o modalitate


perimată de a încheia execuţia programului (prin salt la această adresă).
La deplasamentul 5 se găseşte o instrucţiune de apel FAR a rutinei de
tratare a întreruperii 21h, care este dispecerul funcţiilor DOS.
Începând cu deplasamentul 0Ah se găsesc adresele de revenire la terminarea
programului (identică cu vectorul întreruperii 22h), a rutinei de tratare a acţionării
combinaţiei de taste CTRL-BREAK sau CTRL-C (vectorul întreruperii 23h),
respectiv a rutinei de tratare a erorilor critice (vectorul întreruperii 24h). Aceste
adrese sunt cele valabile pe parcursul execuţiei programului proprietar al PSP. Ele
se folosesc pentru restaurarea vectorilor de întrerupere 22h, 23h, respectiv 24h,
atunci când se revine dintr-un proces fiu.
Adresa procesului părinte, de la deplasamentul 16h este pentru
COMMAND.COM, 0 sau adresa propriului PSP.
Blocul de environment al programului, a cărui adresă de segment este
precizată la deplasamentul 2Ch, conţine setări de variabile sistem cu valabilitate
locală (modificarea lor nu influenţează procesul părinte). Aceste setări apar sub
forma unei succesiuni de şiruri ASCIIZ (şir ASCII terminat printr-un octet de
valoare 0), de forma variabila=valoare. Această succesiune se termină printr-un
octet 0 suplimentar (deci la sfârşit vom avea doi octeţi cu valoarea 0). După aceasta
urmează un cuvânt cu valoarea 1, urmat de numele programului proprietar
717

specificat cu întreaga cale şi cu extensie. Specificatorul programului se încheie


printr-un octet de valoare 0.
Cele două structuri FCB implicite, de la deplasamentele 5Ch şi 6Ch se
suprapun parţial, deci nu se va putea lucra cu ambele în acelaşi timp. Oricum FCB
reprezintă o modalitate perimată de a lucra cu fişiere. În locul lor se folosesc
identificatori de fişiere.
La deplasamentul 81h se găseşte linia de comandă ce a apelat programul
proprietar, mai puţin numele fişierului executabil ce conţine programul şi
redirectările (aici pot fi găsiţi parametrii programului). Lungimea şirului astfel
rămas este specificată la deplasamentul 80h. Peste această zonă, începând de la
deplasamentul 80h, pe o lungime de 128 octeţi se întinde zona DTA implicită.
Această zonă este folosită de către anumite funcţii DOS ce lucrează cu discul (cele
ce folosesc FCB şi funcţiile FIND FIRST şi FIND NEXT). Pentru a nu pierde
informaţiile din linia de comandă, înainte de a realiza operaţii cu discul, trebuie fie
salvată linia de comandă într-o altă zonă de memorie, fie schimbată adresa zonei
DTA (folosind funcţia DOS 1Ah).

Aspecte specifice ale formatului COM

Programele de tip COM sunt cele cu model de memorie tiny. Acestea


folosesc un singur segment, unde se găsesc datele, codul şi stiva. În cadrul unui
astfel de program se fac doar apeluri de tip NEAR, deci adresa de segment a
programului nu apare în mod explicit în nici o instrucţiune din program (codul
programului rămâne neschimbat indiferent de adresa de segment la care este
încărcat în memorie).
La scrierea programului trebuie marcat în mod explicit spaţiul pentru PSP,
care este şi el inclus în acelaşi segment cu restul programului, printr-o directivă
ORG 100h. Aceasta are rolul de a asigura calculul corect al deplasamentelor în
cadrul segmentului (toate deplasamentele se măresc cu 100h din cauza PSP, care
ocupă primii 256 de octeţi din segment).
În fişierul cu extensia COM nu se rezervă spaţiu pentru PSP. PSP este creat
şi iniţializat de către sistemul de operare, care încarcă în continuarea sa întreg
conţinutul fişierului COM, fără nici o modificare. Apoi iniţializează regiştrii de
segment DS, ES şi SS cu adresa segmentului programului; SP este iniţializat cu
valoarea FFFEh, deplasament la care este pus un cuvânt cu valoarea 0. Acest lucru
este făcut în ideea de a putea încheia execuţia programului printr-o instrucţiune
RET, care ar extrage de pe stivă valoarea 0 şi s-ar executa în felul acesta un salt la
deplasamentul 0 din cadrul segmentului programului, care este de fapt
deplasamentul 0 al PSP şi care conţine întotdeauna codul unei instrucţiuni INT
20h. Aceasta este o modalitate învechită de a încheia execuţia programului.
În finalul procesului de încărcare a programului în memorie se iniţializează
regiştrii CS şi IP cu adresa de segment a programului, respectiv valoarea 100h,
718

pentru a executa un salt la locaţia de memorie imediat următoare PSP. Acesta este
întotdeauna punctul de intrare într-un program de tip COM.

Aspecte specifice ale formatului EXE

Programele executabile în format EXE folosesc orice model de memorie,


deci nu există restricţii privind numărul de segmente. De asemenea, codul, datele şi
stiva se pot găsi în segmente separate, chiar mai multe pentru fiecare în parte, sau
se pot combina între ele. Într-un astfel de program coexistă atât apeluri de tip
NEAR, cât şi apeluri de tip FAR. Aceasta face ca programele de tip EXE să fie
dependente de locul unde se încarcă în memorie, adică vor exista în program
simboluri a căror valoare depinde de adresa de segment începând de la care se
încarcă programul. De aceea este nevoie ca la încărcarea acestor programe în
memorie să se facă o operaţie de relocare, adică de recalculare a acelor valori
dependente de adresa de segment începând de la care se face încărcarea
programului în memorie.
Punctul de intrare în program nu mai este fix, el putându-se găsi oriunde
doreşte programatorul, care precizează acest punct în cadrul directivei END.
Deşi programul se încarcă tot imediat după PSP propriu, acesta nu mai este
inclus în nici unul din segmentele programului şi de aceea, la scrierea programului
nu mai este nevoie să se precizeze locul pe care îl va ocupa PSP.
Fişierul ce conţine un program în format EXE conţine pe lângă imaginea
programului în memorie, mai puţin PSP şi un header ce conţine o serie de
informaţii necesare la încărcarea şi lansarea în execuţie a programului. Acest
header are următoarea structură:
719

Tabelul 23.3.
Offset Lungime Semnificaţie
00h 2 octeţi „MZ” (semnătura fişierelor .EXE)
02h 1 cuvânt PartPag = lungime fişier modulo
512
04h 1 cuvânt PageCnt = lungime fişier în pagini
de 512 octeţi
06h 1 cuvânt ReloCnt = număr de elemente din
tabela de relocare
08h 1 cuvânt HdrSize = dimensiune header în
paragrafe de 16 octeţi
0Ah 1 cuvânt MinMem = necesar minim de
memorie după sfârşitul programului
(în paragrafe)
0Ch 1 cuvânt MaxMem = necesar maxim de
memorie după sfârşitul programului
(în paragrafe)
0Eh 1 cuvânt ReloSS = deplasament segment
stivă
10h 1 cuvânt ExeSP = offset vârf stivă la
lansarea în execuţie
12h 1 cuvânt ChkSum = sumă de control
14h 1 cuvânt ExeIP = offset adresă de start
16h 1 cuvânt ReloCS = deplasament segment de
cod
18h 1 cuvânt TablOff = offset tabelă de relocare
1Ah 1 cuvânt indicator de overlay (0 pentru
modulele de bază)
TablOff 2*ReloCnt tabela de relocare
cuvinte
? ? octeţi caractere până la limita de paragraf

Necesarul minim de memorie după sfârşitul programului este determinat de


regulă de stivă, care nu este inclusă în fişier. Necesarul maxim de memorie după
sfârşitul programului este implicit FFFFh. De aceea, de regulă, programele aflate în
execuţie ocupă toată memoria disponibilă.
Deplasamentele segmentelor de stivă şi de cod sunt faţă de factorul de
relocare (adresa de segment la care se încarcă programul).
Valorile regiştrilor SP şi IP la lansarea în execuţie a programului (ExeSP şi
ExeIP) sunt valorile precizate în directivele STACK, respectiv END, din sursa
programului.
Tabela de relocare conţine câte un dublu cuvânt pentru fiecare element al
tabelei. Un astfel de dublu cuvânt este format din deplasamentul şi deplasamentul
segmentului faţă de factorul de relocare corespunzătoare elementului din program
căruia trebuie să i se aplice operaţia de relocare.
720

Încărcarea şi lansarea în execuţie a unui program în format EXE presupune


următoarele operaţii:
 se citesc 1Ch octeţi din fişierul .EXE (porţiunea formatată a header-
ului), într-o zonă locală de memorie.
 se determină lungimea modulului inclus în fişierul .EXE:

lg_modul = (PageCnt - 1)  512 + PartPag – HdrSize  16

în cazul în care conţinutul variabilei PartPag este diferit de 0 sau

lg_modul = PageCnt  512 – HdrSize  16,

în cazul în care PartPag = 0.


 se alocă memorie conform cu lungimea astfel determinată şi cu valorile
MinMem şi MaxMem. Dacă nu se pot aloca minimum 256 +
lung_modul + MinMem*16 octeţi, atunci încărcarea şi lansarea în
execuţie a programului nu este posibilă.
 se creează PSP.
 se determină deplasamentul în fişier al modulului: HdrSize*16 .
 se citeşte modulul de program în memorie, la adresa START_SEG:0000,
unde START_SEG este uzual adresa de segment a PSP plus 10h.
 se setează poziţia de citire din fişier la începutul tabelei de relocare
(TablOff).
 pentru fiecare element al tabelei de relocare (ReloCnt) se execută
următoarele operaţii:
 se citeşte elementul tabelei din fişier (două cuvinte: I_OFF şi I_SEG).
 se determină adresa actuală a elementului ce trebuie realocat:
RELO_SEG = (START_SEG + I_SEG).
 se adună START_SEG la cuvântul de la adresa RELO_SEG:I_OFF.
 se iniţializează regiştrii semnificativi şi se dă controlul programului:
o ES = DS = adresa de segment a PSP;
o SS = START_SEG + ReloSS;
o SP = ExeSP;
o CS = START_SEG + ReloCS;
o IP = ExeIP.

23.6 Probleme specifice în realizarea programelor rezidente

Structura unui program rezident

Pentru ca un program activ în memoria internă a calculatorului să rămână


rezident este nevoie ca acesta să apeleze funcţia TSR a sistemului fie prin INT 27h,
721

fie prin funcţia DOS 31h. Această funcţie menţine rezervată prima parte a zonei de
memorie ocupată de program, parte a cărei dimensiune este transmisă rutinei de
tratare a întreruperii apelate. Zona de memorie imediat următoare părţii marcate ca
rezidentă este eliberată şi apoi controlul este redat sistemului de operare sau
programului ce a lansat în execuţie programul rezident, dacă există un astfel de
program.
Orice program rezident este constituit din două mari părţi: o parte rezidentă
şi o parte nerezidentă. Partea nerezidentă trebuie să fie plasată în program după
partea rezidentă, deoarece funcţia TSR păstrează în memorie partea de început a
programului şi eliberează memoria ocupată de partea finală a programului.
0
P.S.P.
100h
jmp etichetă_nerezident

Date modul rezident

Rutine de tratare a
întreruperilor
int_x1: int_x2:

............
etichetă_nerezident:
Bloc decizie
instalare/dezinstalare/alte
operaţii
Date modul nerezident

Modul de instalare

apel funcţie TSR


Modul de dezinstalare

Alte module (configurare)

apel funcţie EXIT

Figura 23.4 – Schema generală a imaginii în memorie a unui program


rezident, înainte de apelul funcţiei TSR
722

Partea nerezidentă a programului

Partea nerezidentă conţine modulul de instalare a programului rezident şi,


eventual, cel de dezinstalare, module care nu se justifică să fie păstrate rezidente în
memorie, deoarece ele se execută o singură dată în procesul de utilizare a unui
program rezident. Aceasta înseamnă că după încărcarea şi predarea controlului
programului, partea nerezidentă este cea care trebuie să se execute. Ca atare la
punctul de intrare în program, care se găseşte la deplasamentul 100h, imediat după
PSP, va trebui să existe o instrucţiune de salt necondiţionat la adresa de început a
părţii nerezidente.
La începutul părţii nerezidente va trebui să existe un modul care să decidă
acţiunea ce se va executa în continuare. De regulă, acest modul începe prin a
verifica dacă în memoria RAM se găseşte deja o instanţă a programului rezident,
după care decide ce acţiune să întreprindă, în funcţie de rezultatul acestei verificări:
 dacă în memorie nu se mai găseşte o altă instanţă a programului
rezident, se va trece la instalarea programului rezident;
 dacă în memorie se găseşte deja o instanţă a programului rezident,
atunci în continuarea se poate face o dezinstalare a programului rezident
aflat în memoria RAM (de fiecare dată când se întâlneşte această
situaţie sau doar când utilizatorul specifică în mod explicit că doreşte
acest lucru).
Alegerea între execuţia instalării sau a dezinstalării se poate face şi altfel: de
exemplu, prin specificarea unor parametri în linia de comandă, atunci când este
lansat în execuţie programul sau prin alegerea unei opţiuni dintr-un meniu.

Partea rezidentă a programului

Partea rezidentă a programului conţine PSP al programului, urmat de o


instrucţiune de salt la începutul părţii nerezidente şi apoi de datele şi codul
modulului rezident.
În zona de date se poate găsi şi o stivă a modulului rezident. Aceasta nu este
obligatoriu să existe. Dacă există, ea trebuie foarte atent dimensionată deoarece în
cazul programelor rezidente risipa de spaţiu nu este permisă, dar în acelaşi timp
stiva trebuie să fie suficient de mare pentru a nu se produce în nici un caz depăşire
de stivă. O astfel de depăşire de stivă ar duce cu o foarte mare probabilitate la
blocarea sistemului.
Partea de cod este formată din rutinele de tratare a întreruperilor deturnate
de către programul rezident. În raport cu rutinele existente în sistem la momentul
deturnării, rutinele programului rezident pot veni în completarea acestora sau
urmează a fi executate alternativ cu vechile rutine, în funcţie de satisfacerea sau
nesatisfacerea anumitor condiţii la momentul apariţiei întreruperii, sau pot să
723

înlocuiască complet vechile rutine. În fapt, indiferent de varianta aleasă, noua


rutină o înlocuieşte pe cea veche, dar dacă se doreşte şi executarea vechii rutine,
atunci rutina nou instalată trebuie să apeleze rutina înlocuită prin simularea unui
apel INT:

pushf
cli
call dword ptr CS:vechea_rutina

Toate rutinele conţinute de către partea rezidentă a programului trebuie să


respecte următoarele condiţii:
 păstrarea nealterată a contextului;
 respectarea modului de transmitere a parametrilor existent în rutina
originală (dacă este cazul);
 evitarea generării de noi întreruperi;
 evitarea activărilor multiple;
 minimizarea timpului de execuţie.
În ceea ce priveşte revenirea la programul întrerupt, aceasta se poate face în
două moduri:
 cu o instrucţiune IRET, care este varianta normală de întoarcere şi care
presupune restaurarea automată a tuturor indicatorilor de condiţie la
starea în care se aflau imediat înainte de întrerupere;
 cu o instrucţiune RETF 2, variantă în care indicatorii de condiţie nu mai
sunt restauraţi. Această variantă este folosită de anumite întreruperi
software care comunică programului apelant anumite informaţii privind
modul de încheiere a tratării întreruperii, prin intermediul indicatorilor
de condiţie. La deturnarea unei întreruperi, programatorul trebuie să
respecte convenţia de comunicare între rutina originală şi programele
apelante şi să folosească metoda adecvată de revenire în programul
apelant.

Comutarea contextului

Comutarea contextului este obligatorie pentru orice rutină de tratare a unei


întreruperi, deoarece aceasta trebuie să păstreze contextul nealterat. De aceea,
înainte de a începe tratarea propriu-zisă a întreruperii, orice rutină trebuie să se
îngrijească de salvarea contextului programului întrerupt şi de comutarea către
contextul rutinei, iar după încheierea tratării întreruperii trebuie să comute înapoi
pe contextul salvat al programului.
Următoarele elemente trebuie să fie avute în vedere atunci când se face
comutarea contextului:
 regiştrii procesorului;
724

 comutarea pe stiva proprie a modulului rezident — acest lucru este


necesar dacă modulul rezident a fost prevăzut cu o stivă proprie şi se
realizează prin iniţializarea corespunzătoare a regiştrilor SS şi SP, care
anterior trebuie să fie salvaţi într-o zonă de memorie special rezervată în
modulul rezident. La încheierea tratării întreruperii este necesară
comutarea înapoi pe stiva programului întrerupt.
 salvarea stivei programului întrerupt — salvarea se va face într-o zonă
rezervată special în modulul rezident. Salvarea stivei curente în
momentul întreruperii este justificată doar pentru acele rutine care
apelează alte întreruperi sau care nu se asigură de dezactivarea
întreruperilor pe tot parcursul rutinei. Se recomandă salvarea a 64 de
cuvinte din stiva programului întrerupt.
 PSP şi DTA — este necesar să fie salvate adresele acestor structuri ale
programului întrerupt atunci când se fac operaţii cu discul folosind
funcţii DOS. După salvarea acestor adrese, trebuie să fie marcate ca
active PSP şi DTA ale modulului rezident. La încheierea tratării
întreruperii trebuie setate PSP şi DTA active, la valorile salvate.
 conţinutul memoriei video, modul video, poziţia şi tipul cursorului,
poziţia pointerului mouse-ului (dacă există driver de mouse).

Instalarea programelor rezidente

Instalarea programelor rezidente presupune iniţializarea corespunzătoare a


datelor necesare modulului rezident şi realizarea legăturilor necesare acestuia şi se
încheie prin apelul funcţiei TSR. De regulă nu este necesară prezenţa simultană în
memorie a mai multor instanţe ale aceluiaşi program rezident. De aceea, înainte de
a se trece la instalarea programului este necesară verificarea existenţei unei instalări
anterioare. După ce a fost verificată existenţa unei instalări precedente şi s-a
confirmat lipsa ei, deci posibilitatea realizării unei instalări, se trece la realizarea
următoarelor operaţii necesare pentru instalarea programului rezident:
 iniţializări ale parametrilor programului, salvarea în zona de date a
modulului rezident a datelor necesare şi alte operaţii de iniţializare, în
funcţie de particularităţile programului rezident;
 salvarea adreselor rutinelor de tratare a întreruperilor ce vor fi deturnate;
 deturnarea întreruperilor;
 eliberarea, dacă este cazul, a blocurilor de date alocate suplimentar de
către modulul de iniţializare şi nenecesare modulului rezident;
 eliberarea blocului de environment (opţional);
 determinarea zonei de memorie ce va fi rezervată pentru modulul
rezident şi pregătirea regiştrilor pentru apelul funcţiei TSR;
725

Modulul de instalare se încheie prin apelul funcţiei Terminate and Stay


Resident care rezervă memoria pentru modulul rezident şi apoi redă controlul
sistemului de operare sau programului apelant dacă este cazul.

Verificarea unei instalări precedente

Această metodă se bazează pe faptul că programul rezident instalat


deturnează anumite întreruperi, astfel încât noii vectori ai întreruperilor respective
indică rutine care sunt incluse în modulul rezident. De aceea, dacă programul
rezident a fost instalat, atunci adresele rutinelor de tratare a întreruperilor pe care le
foloseşte programul rezident vor indica locaţii de memorie din interiorul
segmentului ocupat de acesta.
Pentru a stabili dacă în segmentul indicat de vectorul unei anumite
întreruperi se află programul nostru rezident este suficient să verificăm conţinutul
unei anumite zone de memorie aflată la un deplasament bine precizat în interiorul
segmentului în cauză şi să vedem dacă găsim acolo semnătura programului
rezident. Această semnătură constă într-un şir de valori prestabilite, pe care
programul rezident le va conţine întotdeauna la acel deplasament.
În consecinţă, determinarea existenţei unei instalări precedente a
programului rezident presupune următoarele operaţii:
 în zona de date a modulului rezident, programatorul va rezerva un
număr de octeţi ce vor conţine un şir de valori prestabilite.
 în partea nerezidentă, blocul de verificare a unei instalări precedente va
conţine următoarele operaţii: determinarea adresei de segment a uneia
din întreruperile cunoscute ca fiind deturnate de către programul
rezident; adăugarea la adresa de segment astfel obţinută a
deplasamentului semnăturii; extragerea de la adresa fizică astfel
obţinută a şirului de valori şi apoi compararea sa cu semnătura
programului rezident. Dacă sunt identice, atunci programul a fost deja
instalat, altfel programul nu este instalat.

Verificarea în lanţul de blocuri MCB

O altă metodă de testare a existenţei unei instalări precedente a programului


rezident este aceea de a verifica toate blocurile de memorie MCB (Memory Control
Blocks) şi a vedea dacă vreunul dintre ele este rezervat pentru programul nostru
rezident.
Orice bloc de memorie ce urmează nucleului sistemului de operare este
precedat de un bloc MCB de 16 octeţi, care conţine la deplasamentul 8 un şir de 8
caractere ce reprezintă numele proprietarului zonei respective de memorie, dacă
zona este rezervată (vezi 2.1.2.). Deci aici se găseşte numele programului ce ocupă
726

respectiva zonă de memorie. Adresa de segment a primului MCB este găsită


folosind funcţia DOS 52h.
Adresa de segment a primului MCB se găseşte la deplasamentul negativ -2
în tabela INVARS. După apelul funcţiei DOS 52h, adresa de segment a primului
MCB este dată de cuvântul indicat prin pointerul ES:BX-2.
Adresa de segment a următorului MCB se determină adunând la adresa de
segment a MCB curent valoarea dim (dimensiunea în paragrafe a zonei de memorie
referite) a cuvântului de la deplasamentul 3 din cadrul MCB curent şi valoarea 1,
care reprezintă dimensiunea în paragrafe a unui MCB (vezi 2.1.2., formula (2)).
Ultimul MCB din lanţ are caracterul „Z” la deplasamentul 0.

Folosirea întreruperii multiplex

Această metodă se bazează pe întreruperea 2Fh, numită întrerupere


multiplex, care este pusă la dispoziţie de către sistemul de operare MS-DOS.
Această întrerupere s-a intenţionat a fi o modalitate de comunicare între procese.
Fiecare modul (proces) rezident trebuie să aibă un identificator multiplex, care
pentru procesele utilizator poate fi orice număr între C0h şi FFh. Numerele
cuprinse între 00h şi BFh sunt rezervate sistemului de operare DOS. Toate
modulele rezidente trebuie să intercepteze această întrerupere. În rutina de tratare
trebui să verifice identificatorul multiplex din registrul AH şi dacă acesta este
identificatorul propriu, atunci execută funcţia solicitată prin registrul AL, pe care ar
încheia-o printr-o instrucţiune IRET. Dacă în AX nu este găsit identificatorul
propriu, atunci trebuie să predea controlul următorului handler din lanţ apelând
rutina a cărei adresă era pusă în tabela vectorilor de întrerupere înainte ca
programul să fi deturnat întreruperea.
Orice program rezident ce interceptează această întrerupere trebuie să
implementeze măcar funcţia 00h care determină dacă un program rezident a fost
sau nu instalat. Această funcţie trebuie să returneze în AL 00h dacă programul nu
este instalat sau FFh dacă programul este instalat.
În concluzie, este necesar ca programul rezident să primescă un număr
propriu de identificare cuprins între C0h şi FFh şi să prevadă o rutină de tratare a
întreruperii 2Fh care să realizeze următoarele operaţii:
727

 testează conţinutul registrului AH;


 dacă acesta este identic cu numărul propriu de identificare multiplex,
execută următoarele operaţii; testează conţinutul registrului AL; dacă
acesta este 00h, pune în AL valoarea FFh; dacă este alt număr decât
00h, apelează procedura de tratare a funcţiei respective, dacă există;
 dacă AH nu conţine numărul propriu de identificare multiplex, apelează
vechea rutină de tratare a întreruperii prin simularea unei instrucţiuni
INT.
Blocul din modulul nerezident, care testează dacă programul rezident a fost
sau nu instalat trebuie doar să apeleze întreruperea 2Fh (după ce a fost pus în AH
identificatorul multiplex al programului, iar în AL valoarea 00h) şi să testeze
rezultatul:
 dacă AL are valoarea 00h, programul nu a fost instalat;
 dacă AL are valoarea FFh, programul a fost instalat.

Dezinstalarea programelor rezidente

Dezinstalarea programelor rezidente nu este obligatorie, dacă realizatorul


programului consideră că este justificată prezenţa programului rezident în memoria
calculatorului până la sfârşitul sesiunii curente de lucru.
Dacă este prezent, modulul de dezinstalare trebuie mai întâi să verifice dacă
este posibil să se recurgă la dezinstalare fără a afecta buna funcţionare a sistemului.
Această verificare este necesară deoarece este posibil ca după instalarea
programului nostru să mai fi fost instalate şi alte programe rezidente ce au deturnat
aceleaşi întreruperi. În acest caz, tentativa de refacere a vechilor legături va duce la
tăierea legăturilor programelor rezidente instalate ulterior programului nostru.
Verificarea posibilităţii de dezinstalare se va face comparând toţi vectorii de
întrerupere ce au fost interceptaţi de programul nostru, cu adresele rutinelor din
modulul rezident.
Dacă în urma acestei verificări a rezultat că este posibilă dezinstalarea,
modulul de dezinstalare va reface vechii vectori de întrerupere pe baza adreselor
salvate la instalare, după care va elibera blocurile de memorie alocate pentru
eventuale date suplimentare ale modulului rezident, pentru environment şi pentru
modulul rezident al programului.

23.7 Activarea programelor rezidente

Activarea programelor rezidente presupune preluarea controlului de către


programul rezident şi realizarea de către acesta a funcţiei proprii, atunci când sunt
îndeplinite anumite condiţii.
728

Un program rezident, pentru a-şi putea îndeplini funcţia, interceptează una


sau mai multe întreruperi. În felul acesta, la orice solicitare a uneia dintre
întreruperile respective, de către sistem sau de către un program utilizator,
programul rezident primeşte controlul. Apoi el decide în funcţie de îndeplinirea sau
neîndeplinirea anumitor condiţii, dacă să activeze rutina proprie sau să apeleze doar
vechea rutină de tratare a întreruperii respective sau să facă alte operaţii. Aceste
condiţii se referă la a testa dacă este necesară activarea programului rezident, dar
trebuie să se refere şi la a testa dacă este posibilă activarea programului rezident
fără a afecta buna funcţionare a sistemului. Cu alte cuvinte, există anumite restricţii
cu privire la momentul în care un program rezident se poate activa.

Restricţii care trebuie să fie avute în vedere înainte de activarea


programului rezident

Interdicţia întreruperii unei funcţii DOS se datorează faptului că funcţiile


DOS nu sunt reentrante. Cu alte cuvinte, dacă execuţia unei funcţii DOS este
întreruptă, şi apoi este apelată din nou aceeaşi funcţie sau altă funcţie DOS, atunci
datele primei funcţii pot fi suprascrise de noua funcţie apelată. Funcţiile DOS nu
prevăd posibilitatea unor apeluri multiple şi folosesc aceleaşi zone de date.
Pentru acest motiv, o rutină ce apelează funcţii DOS nu are voie să fie
activată în cursul execuţiei unei funcţii DOS. Interdicţia poate fi extinsă asupra
tuturor rutinelor ce apelează alte întreruperi sau care nu menţin întreruperile
dezactivate pe tot parcursul execuţiei indiferent dacă e vorba de apelarea unor
funcţii DOS sau nu. Această precauţie trebuie luată deoarece este posibil ca
întreruperile apelate în timpul execuţiei rutinei să apeleze ele funcţii DOS sau este
posibil ca aceste întreruperi să fie interceptate de alte programe rezidente ce
folosesc funcţii DOS.
Pentru a evita activarea programului rezident când a fost întreruptă o funcţie
DOS, se va testa la intrarea în rutina de tratare a întreruperii un indicator intern al
sistemului de operare DOS, indicator numit INDOS, care este diferit de 0 atunci
când este în curs de execuţie o funcţie DOS. Adresa acestui indicator se poate
obţine cu ajutorul funcţiei DOS 34h.
Această funcţie se va apela la instalarea programului rezident şi se va
memora adresa indicatorului INDOS într-o locaţie din zona de date a modulului
rezident. În felul acesta se va evita apelul funcţiei DOS pe parcursul tratării
întreruperii interceptate. Adresa indicatorului INDOS se stabileşte la instalarea
sistemului de operare şi nu se modifică pe parcursul sesiunii de lucru (până la
resetarea sistemului).
Acest indicator nu rezolvă complet problema deoarece aproape întotdeauna
el este diferit de 0. Acest lucru se întâmplă deoarece toate operaţiile pe care le
execută sistemul de operare folosesc funcţii DOS. De exemplu, interpretorul de
comenzi COMMAND foloseşte o funcţie DOS pentru a citi comanda de la
729

prompter. Ceea ce înseamnă că mai mereu această funcţie este activă. Pentru a
rezolva această problemă, sistemul de operare MS-DOS pune la dispoziţie
întreruperea INT 28h pe care sistemul o apelează de fiecare dată când se află în
aşteptare (de exemplu când interpretorul COMMAND aşteaptă introducerea unei
comenzi).
Această întrerupere indică faptul că programele rezidente se pot activa în
siguranţă. Rutina originală a întreruperii execută un simplu IRET. Programele
rezidente pot intercepta această întrerupere pentru a se activa la apelul ei. Pe
parcursul tratării acestei întreruperi pot fi apelate în siguranţă toate funcţiile DOS al
căror număr este mai mare decât 0Ch, cu menţiunea că funcţiile DOS 3Fh şi 40h nu
trebuie să folosească un handler ce referă dispozitivul CON.

Interdicţia întreruperii activităţilor critice în timp

Programelor rezidente le este interzis să se activeze în timp ce se execută o


rutină critică din punct de vedere al timpului de execuţie. Dacă nu se respectă
această interdicţie, atunci datorită intervenţiei programului rezident vor apare
întârzieri în execuţia rutinei critice, deci desincronizări în lucrul cu perifericul la
care se referă rutina şi ca urmare pot apare pierderi de date.
Pentru a preveni acest lucru, programul rezident va trebui să intercepteze
toate rutinele critice în timp şi să execute următoarele operaţii în noua rutină:
 setare indicator de execuţie rutină critică în timp;
 apel rutină originală;
 resetare indicator de execuţie rutină critică în timp.
În felul acesta, pe parcursul execuţiei rutinei critice, programul rezident va
avea un indicator propriu setat corespunzător şi care îi va spune că nu se poate
activa. Deci, la orice tentativă de activare, programul rezident va trebui să verifice
mai întâi situaţia acestui indicator.

Interdicţia întreruperii tratării erorilor critice

Programele rezidente nu trebuie să întrerupă rutina care tratează o eroare


critică pentru a nu provoca modificarea codului de eroare şi a nu afecta astfel
execuţia programului întrerupt. Pentru a şti când are loc tratarea unei erori critice,
programul rezident poate testa un indicator a cărui adresă se obţine cu ajutorul
funcţiei DOS 5Dh, subfuncţia 6.
Indicatorul de eroare critică se reprezintă pe un cuvânt şi este diferit de 0
dacă este în curs de tratare o eroare critică.
La instalarea programului rezident se va apela funcţia DOS 5Dh, subfuncţia
6h şi se va memora adresa conţinută în DS:SI la o locaţie din modulul rezident. În
felul acesta se va evita apelarea funcţiei DOS de către rutina de tratare a întreruperii
interceptate. La apariţia unei tentative de activare a programului rezident, se va
730

testa indicatorul de eroare critică şi dacă acesta este diferit de 0 se va renunţa la


activarea programului rezident.

Evitarea activărilor multiple

Trebuie avută în vedere şi posibilitatea ca în timp ce se execută programul


rezident să mai apară o cerere de activare a sa. Pentru ca şi această cerere să fie
satisfăcută fără a afecta execuţia corectă a programului de la prima activare ar
trebui ca programul rezident să fie reentrant. Dar cum scrierea unui cod reentrant
ridică probleme mult mai dificile, este preferată varianta mai simplă de a împiedica
o a doua activare a programului rezident în timp ce se execută deja o instanţă a sa.
Acest lucru se realizează folosind un indicator propriu al programului
rezident care se setează înaintea fiecărei activări a programului şi se resetează la
încheierea execuţiei. Înainte de a activa programul, se va testa acest indicator. Dacă
este setat înseamnă că există deja în execuţie o instanţă a programului, care a fost
întreruptă. În acest caz se va renunţa la activarea programului.

23.8 Program rezident pentru afişarea ceasului

Acest program rezident îşi propune să ofere programelor utilizator un


serviciu de afişare a unui ceas de timp real şi servicii de cronometrare.
Programul rezident va pune la dispoziţia programelor o întrerupere software
de utilizator care va cuprinde următoarele funcţii:
 determinarea stării ceasului sau a cronometrului;
 afişarea ceasului;
 dezactivarea ceasului;
 activarea cronometrului pentru o numărătoare în sens crescător;
 activarea cronometrului pentru o numărătoare inversă;
 oprirea cronometrului;
 repornirea cronometrului;
Funcţionarea ceasului sau a cronometrului se va face în paralel cu execuţia
oricărui program utilizator. Programul rezident va permite funcţionarea alternativă
a ceasului sau a cronometrului.
Funcţia pentru determinarea stării ceasului sau cronometrului va returna
modul de funcţionare a programului rezident (ceas, cronometru sau dezactivat), iar
pentru cronometru va returna şi timpul indicat de acesta.
Cronometrul va fi iniţializat cu un timp de start, iar pentru cronometrul
crescător se va preciza şi limita maximă de timp de funcţionare. Repornirea
cronometrului după o oprire se va face de la momentul la care fusese oprit.
Afişarea ceasului sau a cronometrului nu este obligatorie dacă sistemul
video funcţionează în mod grafic sau dacă este în execuţie o rutină critică, dar este
obligatoriu ca scurgerea timpului să fie măsurată şi în aceste condiţii.
731

Precizia oferită de aceste servicii va fi la nivel de secundă.

Modul de utilizare a programului

Pentru a oferi serviciile solicitate, programul rezident pune la dispoziţia


programelor utilizator întreruperea 88h, a cărei rutină nouă va oferi următoarele
servicii:
 serviciul 0 – determinarea stării ceasului: la apel AH=0; returnează în
CX numărul total de secunde reprezentat de timpul indicat de
cronometru, în AL returnează modul de lucru al programului rezident,
iar în AH returnează modul de lucru anterior ultimei opriri a
cronometrului;
 serviciul 1 – oprirea ceasului sau cronometrului: la apel AH=1; nu
returnează nimic;
 serviciul 2 – activarea cronometrului: la apel AH=2, AL=0 pentru
cronometru crescător sau AL=1 pentru cronometru descrescător, CX
conţine numărul total de secunde reprezentat de timpul de start al
cronometrului, iar pentru AL=0 DX conţine numărul total de secunde
reprezentat de timpul final de funcţionare a cronometrului; nu
returnează nimic;
 serviciul 3 – activarea afişării ceasului de timp real: la apel AH=3; nu
returnează nimic;
 serviciul 4 – repornirea cronometrului din punctul în care a fost oprit: la
apel AH=4; returnează AL=0 dacă cronometrul a fost pornit sau AL=1
dacă cronometrul nu poate fi repornit.

Modul de lucru al programului rezident poate avea una din următoarele


valori:
0 – dezactivat prin serviciul 1;
1 – dezactivat automat la expirarea timpului de funcţionare a cronometrului;
2 – cronometru crescător activ;
3 – cronometru descrescător activ;
4 – afişarea ceasului de timp real activată.
Timpul maxim de funcţionare pentru cronometru este de 65535 de secunde
(18 ore, 12 minute şi 15 secunde).
Solicitarea afişării ceasului de timp real în timpul funcţionării unui
cronometru duce la oprirea cronometrului. Acesta poate fi repornit ulterior.
Repornirea cronometrului este posibilă doar dacă există un cronometru oprit
din funcţionare înainte de expirarea timpului său de funcţionare, prin serviciul 1
sau prin activarea afişajului ceasului de timp real. Activarea unui cronometru în
timpul funcţionării altui cronometru duce la distrugerea cronometrului care era în
funcţiune (care nu mai poate fi repornit). Pentru a obţine detalii privind cauza ce
732

face imposibilă folosirea serviciului 4 (când returnează AL=1) se poate apela


servicul 0, care indică atât modul de lucru curent, cât şi modul de lucru salvat la
ultima oprire a unui cronometru.
Pentru a beneficia de aceste servicii, programul trebuie să fie instalat în
memorie. Acest lucru se face lansând în execuţie programul executabil
CEAS.COM.
Pentru dezinstalarea programului se lansează încă odată în execuţie
programul CEAS.COM care va solicita confirmarea intenţiei de dezinstalare:
Programul este deja instalat!
Doriti dezinstalarea lui? (d/n)

Dacă se apasă d sau D, atunci se încearcă dezinstalarea programului


rezident, după care se afişează rezultatul tentativei de dezinstalare: programul a fost
dezinstalat sau acest lucru nu este posibil.

Modul de realizare a programului

Modulul rezident al programului conţine datele acestuia (indicatorii


modului de lucru, indicatori pentru timpul scurs, indicatori de activare, adresele
rutinelor deturnate, regiştrii SS şi SP salvaţi), o stivă proprie de 32 de cuvinte şi
rutinele de tratare a întreruperilor 8h, 10h, 13h şi 88h:

org 100h ;specificare zona ocupata de PSP


intrare:
jmp nerezident

mod_lucru db 0 ;mod de functionare


mod_oprit db 0 ;mod de lucru inaintea dezactivarii
semnatura db "CEAS/CRONOMETRU"
lung_sem equ $-semnatura
fract_secunda db 0 ;fractiuni de secunda
corector db 0 ;corecteaza fractiunile pierdute
crono_crt dw ? ;timp indicat de cronometru (s)
crono_max dw ? ;timp functionare cronometru (s)
inbios db 0 ;indicator de rutina critica activa
activ db 0 ;indicator de TSR curent activ
int8_veche label dword ;adresa rutina tratare INT 8
int8_veche_ofs dw ?
int8_veche_seg dw ?
int10h_veche label dword ;adresa rutina tratare INT 10h
int10h_veche_ofs dw ?
int10h_veche_seg dw ?
int13h_veche label dword ;adresa rutina tratare INT 13h
int13h_veche_ofs dw ?
int13h_veche_seg dw ?
ceas db ?,112,?,112,':',112,?,112,?,112,':',112,?,112,?,112
733
sp_int dw ? ;SP al programului intrerupt
ss_int dw ? ;SS al programului intrerupt
sp_int2 dw ? ;SP al TSR-ului
stiva dw 64 dup(?) ;stiva TSR-ului
varf_stiva label word

int_8:
;codul rutinei de tratare int 8
int_10h:
;codul rutinei de tratare int 10h
int_13h:
;codul rutinei de tratare int 13h
int_88h:
;codul rutinei de tratare int 88h

nerezident:
;modulul nerezident

Şirul de octeţi ceas conţine caracterele care reprezintă timpul ce trebuie


afişat. Şirul cuprinde pentru fiecare caracter doi octeţi: primul conţine codul ASCII
al caracterului şi celălalt conţine atributul de culoare al caracterului, care a fost
iniţializat în acest caz cu valoarea 7*16 + 0 = 112, care înseamnă fond gri (7) şi
caractere negre (0). Acest format este cerut de serviciul video INT 10h, funcţia 13,
subfuncţia 2, care este folosit în acest program pentru afişarea timpului:
afis_sir: ;afisez timpul curent obtinut
mov cs:sp_int2,sp ;salvez SP curent
mov ax,cs
mov es,ax
mov bp,offset ceas ;ES:BP=pointer la sirul de afisat
mov cx,8 ;sirul are 8 caractere
mov dx,0000h ;randul 0, coloana 0
mov ax,1302h ;sir cu atribute, nu modific cursor
int 10h ;afisez
mov sp,cs:sp_int2 ;restaurez SP

Salvarea şi restaurarea registrului SP înainte, respectiv după apelul


serviciului video este necesară deoarece acesta distruge conţinutul registrului SP.
Întreruperile 10h şi 13h au fost interceptate pentru a putea seta
corespunzător indicatorul inbios al programului rezident, pe parcursul execuţiei
rutinelor de tratare a acestor întreruperi, rutine care sunt critice din punctul de
vedere al timpului de execuţie:
int10h: ;rutina de tratare a INT 10h
mov cs:inbios,1
pushf
call cs:int10h_veche
mov cs:inbios,0
iret
734

int13h: ;rutina de tratare a INT 13h


mov cs:inbios,1
pushf
call cs:int13h_veche
mov cs:inbios,0
retf 2

Rutina de tratare a întreruperii 88h nu a fost prevăzută cu nici una din


restricţiile de activare specifice programelor rezidente, deoarece nu ea este rutina
de bază a programului rezident. Întreruperea 88h nu este nici întrerupere hardware,
nici întrerupere software de sistem. De aceea ea nu este apelată decât de
programele utilizator şi este practic ca o procedură a programului ce o foloseşte.
Rutina de tratare a întreruperii 88h nu poate întrerupe nici o altă rutină şi de aceea
nu sunt necesare precauţii suplimentare. În plus ea nu execută decât operaţii
secundare, de setare a parametrilor ceasului, conform cu solicitările programelor
utilizator, realizate prin intermediul celor 5 servicii puse la dispoziţie.
În schimb, s-a avut grijă ca la intrarea în rutina de tratare a întreruperii 88h
să fie reactivate întreruperile, pentru ca să nu fie împiedicată buna funcţionare a
sistemului pe parcursul execuţiei acestei rutine.
Rutina principală a programului rezident este rutina de tratare a întreruperii
hardware 8h, generată de ceasul sistem de 18,2 ori pe secundă. Această rutină
începe cu un apel necondiţionat al rutinei originale, după care sunt actualizaţi
indicatorii de scurgere a fracţiunilor de secundă dacă programul este activat (fie
ceasul, fie cronometrul):
;rutina de tratare a INT 8
int8:
pushf
call cs:int8_veche ;apel rutina veche (simulare INT)
push ax
cmp cs:mod_lucru,2
jb sf_int8 ;mod=0 sau 1 ->program inactiv
inc cs:fract_secunda ;mod=2, 3 sau 4 ->program activ
cmp cs:fract_secunda,18 ;s-au facut 18 timer ticks ?
jnz sf_int8 ;... NU->salt sfirsit
inc cs:corector ;... DA->sec. noua in corector
cmp cs:corector,5 ;s-au adunat 5 sec. in corector ?
jnz sec_noua ;... NU->tratez cazul de sec. noua
mov cs:corector,0FFh ;... DA->aplic corectie
dec cs:fract_secunda
jmp sf_int8
sec_noua:
mov cs:fract_secunda,0 ;resetez fractiunile de secunda

Indicatorul fract_secunda marchează fiecare „timer tick” scurs. Când se fac


18 de „timer ticks” avem o nouă secundă, la care indicatorul fract_secunda este
resetat. Dar un „timer tick” este generat de 18,2 ori pe secună şi nu de 18 ori. De
735

aceea trebuie aplicată o corecţie de 1 „timer tick” la fiecare 5 secunde. Pentru


aceasta se foloseşte indicatorul corector care este incrementat de fiecare dată când
indicatorul fract_secunda ajunge la 18. Când indicatorul corector ajunge la 5
(secunde), atunci din fract_secunda se scade un „timer tick” (corecţia) şi se amână
tratarea noii secunde până la următorul „timer tick”. În acest caz, indicatorul
corector este pus pe valoarea 0FFh pentru că la următorul „timer tick”
fract_secunda devine iar 18 şi corector este iar incrementat. În felul acesta este
adus la valoarea corectă 0 (resetare).
Dacă nu s-a produs trecerea la o secundă nouă, atunci rutina se încheie aici.
Dacă s-a produs trecerea la o secundă nouă, atunci trebuie tratat acest caz. Aceasta
înseamnă că trebuie actualizat cronometrul, dacă e activ, şi afişat timpul în varianta
solicitată de modul curent de lucru.
Dacă este activ cronometrul, atunci în continuare trebuie incrementată sau
decrementată locaţia cronometrului, în funcţie de tipul său şi apoi testat dacă a
expirat timpul de funcţionare a cronometrului:

cmp mod_lucru,4 ;testez modul de lucru


jz afis ;mod=4 ->nu e cronometru
cmp mod_lucru,3
jz crono_j ;mod=3 ->cronometru desc.
inc cs:crono_crt ;mod=2 ->cronometru cresc.
mov ax,cs:crono_max
cmp ax,cs:crono_crt ;verific final
jnz afis ;... NU->afisez cronometru crt.
mov cs:mod_lucru,1 ;... DA->dezactivez (timp expirat)
jmp afis ;afisez ultima sec. a cronom.
crono_j:
dec cs:crono_crt ;cronometru descrescator
cmp cs:crono_crt,0 ;verific final
jnz afis ;... NU->afisez cronometru crt.
mov cs:mod_lucru,1 ;... DA->dezactivez (timp expirat)
afis:
;afisare timp curent

După aceasta urmează afişarea timpului curent. De aici înainte, rutina


apelează alte întreruperi. De aceea trebuie să fie verificate restricţiile de activare
înainte de a putea merge mai departe. Problema întreruperii funcţiilor DOS sau a
rutinelor de tratare a unei erori critice nu se pune deoarece nu sunt utilizate nici un
fel de funcţii DOS, respectiv întreruperi care să genereze erori critice. Rămân
restricţiile privind întreruperea unei rutine critice în timp şi problema activării
multiple:
cmp cs:activ,0 ;tentativa de activare multipla ?
jnz rel_sf_int8
cmp cs:inbios,0 ;intrerupere rutina critica ?
jnz rel_sf_int8
736

Dacă este posibilă activarea afişării, atunci trebuie realizată comutarea


contextului, care constă în comutarea stivei şi salvarea regiştrilor. DTA sau stiva
programului întrerupt nu sunt alterate, deci nu trebuie să fie protejate. De
asemenea, cursorul video nu este afectat, deci nu trebuie salvat, iar conţinutul
ecranului nu este afectat decât prin cele 8 caractere ale ceasului, dar această zonă
oricum nu se justifică a fi salvată. Comutarea contextului este realizată astfel:
cli
mov ax,ss ;salvez SS al prog. intrerupt
mov cs:ss_int,ax
mov cs:sp_int,sp ;salvez SP al prog. intrerupt
mov ax,cs
mov ss,ax
mov sp,offset varf_stiva ;comut pe stiva TSR-ului curent
mov cs:activ,1 ;marchez TSR-ul curent activ
sti ;reactivez intreruperile
push bx
push cx
push dx
push es
push bp
push si
push di ;salvez registrii contextului

Reactivarea întreruperilor este realizată deoarece partea de cod ce urmează


necesită un timp de execuţie ceva mai mare.
După salvarea contextului se mai impune o restricţie privind modul video
care trebuie să fie text ca să se facă afişarea. Dacă această restricţie este satisfăcută,
atunci se poate trece la pregătirea şirului ce va fi afişat şi apoi la afişarea propriu-
zisă a acestuia:
mov ah,0Fh
int 10h ;det. mod video si pag. activa
cmp al,3
jbe afiseaza
cmp al,7
je afiseaza
jmp sf_afiseaza ;mod video <> text, nu mai afisez
afiseaza:

Pentru a determina cifrele orelor, minutelor şi secundelor indicate de


cronometru se aplică două împărţiri succesive la 60 a numărului total de secunde
conţinut de contorul cronometrului crono_crt, după care, fiecare rest, respectiv
ultimul cât sunt împărţiţi la 10 pentru a determina cifrele în baza 10. Această
extragere a cifrelor în baza 10 este făcută cu ajutorul macroinstrucţiunii următoare:
737
det_cifre macro cifra_ofs ;;det. cifrele sec., min. sau orelor
mov cl,10
div cl ;;impart nr. la 10 (se afla in AX)
add ah,'0' ;;fac conversia la ASCII
mov cs:ceascifra_ofs+2,ah ;;in AH se afla cifra unitatilor
add al,'0' ;;fac conversia la ASCII
mov cs:ceascifra_ofs,al ;;in AL se afla cifra zecilor
endm

Ceasul de timp real este citit la fiecare secundă cu ajutorul serviciului 2 al


întreruperii 1Ah, care returnează ora, minutul şi secunda în format BCD
împachetat:

mov ah,02h
int 1Ah ;citesc ceasul de timp real
jc defect ;CF=1 -> ceas defect
det_cifre_BCD ch,0 ;determin cifrele orelor
det_cifre_BCD cl,6 ;determin cifrele minutelor
det_cifre_BCD dh,12 ;determin cifrele secundelor

Pentru conversia la ASCII se foloseşte macroinstrucţiunea următoare:

det_cifre_BCD macro reg,cifra_ofs ;;det. 2 cifre din BCD


impachetat
mov ah,0F0h
and ah,reg ;;extrag semioctetul sup. din reg
shr ah,1
shr ah,1
shr ah,1
shr ah,1 ;;mut semioctetul in partea inf.
add ah,'0' ;;convertesc cifra la ASCII
mov cs:ceascifra_ofs,ah ;;scriu cifra zecilor
mov ah,0Fh
and ah,reg ;;extrag semioctetul inf. din reg
add ah,'0' ;;convertesc cifra la ASCII
mov cs:ceascifra_ofs+2,ah ;;scriu cifra unitatilor
endm

După afişarea efectivă a şirului de caractere astfel obţinut, rutina se încheie


cu restaurarea contextului şi IRET.
Partea nerezidentă a programului este formată dintr-un prim bloc ce verifică
existenţa unei instalări precedente, urmat de modulul de instalare şi de modulul de
dezinstalare.
Verificarea existenţei unei instalări precedente se face prin metoda
verificării semnăturii. În acest scop, în blocul de date al modulului rezident a fost
prevăzută semnătura „CEAS/CRONOMETRU”. Se compară şirurile de 15
caractere de la deplasamentul semnatura, din cadrul adresei de segment a rutinei
de tratare a întreruperii 88h, care este egală cu adresa de segment a programului
738

rezident dacă acesta este instalat, respectiv din cadrul adresei de segment a
programului curent. Dacă şirurile sunt identice, atunci programul este deja instalat
şi se apelează modulul de dezinstalare, iar dacă şirurile diferă, atunci programul nu
a fost instalat şi se apelează modulul de instalare:
mov ax,cs
mov ds,ax
mov ax,3588h
int 21h ;ES:BX=adresa rutina INT 88h
mov di,offset semnatura
mov si,di ;DI si SI = offset semnatura
cld
mov cx,lung_sem
repe cmpsb ;compar sirurile DS:SI si ES:DI
jne instalare ;daca diferite, instalare
jmp dezinstalare ;daca identice, dezinstalare

Modulul de instalare salvează adresele rutinelor originale ale întreruperilor


8h, 10h şi 13h, după care deturnează întreruperile 8h, 10h, 13h şi 88h, apoi
eliberează memoria ocupată de blocul de environment şi în final apelează funcţia
TSR.
Eliberarea blocului de environment se face ţinând cont de faptul că adresa
sa de segment se găseşte la deplasamentul 2Ch în cadrul PSP al programului:
mov es,cs:î2Chş ;adr. seg. bloc de environment
mov ah,49h
int 21h ;eliberare environment

Calculul numărului de paragrafe ocupate de modulul rezident se face astfel:


mov ax,offset nerezident ;det. nr. paragrafe modul
rezident
xor dx,dx
mov cx,16
div cx ;AX=paragrafe,DX=octeti suplim.
cmp dx,0
jz fix_paragraf ;DX=0 ->nr. fix de paragrafe
add ax,1 ;DX>0 ->plus 1 paragraf incomplet
fix_paragraf:
mov dx,ax
mov ax,3100h
int 21h ;apel functie TSR

Modulul de dezinstalare dă posibilitatea utilizatorului să aleagă dacă să fie


executată dezinstalarea, iar dacă acesta doreşte, atunci testează dacă este posibilă
dezinstalarea. Pentru aceasta verifică dacă adresa de segment a rutinelor curente de
tratare a întreruperilor 8h, 10h şi 13h este egală cu adresa de segment a
739

programului rezident instalat, găsită prin intermediul întreruperii 88h. Dacă o


singură rutină dintre cele menţionate are o adresă de segment diferită, atunci
înseamnă că un alt program rezident a deturnat acea întrerupere şi în acest caz nu
mai este posibilă dezinstalarea:
mov cx,es ;CX=adr. seg. prog. rezident
mov ax,3508h
int 21h ;citire vector int. 8
mov ax,es ;ES=adr. seg. rutina crt. INT 8
cmp cx,ax
jnz nu_posibil ;INT 8 deturnat de alt TSR
mov ax,3510h
int 21h ;citire vector int. 10h
mov ax,es ;ES=adr. seg. rutina crt. INT 10h
cmp cx,ax
jnz nu_posibil ;INT 10h deturnat de alt TSR
mov ax,3513h
int 21h ;citire vector int. 13h
mov ax,es ;ES=adr. seg. rutina crt. INT 13h
cmp cx,ax
jnz nu_posibil ;INT 13h deturnat de alt TSR

Dacă dezinstalarea este posibilă, atunci se refac vectorii de întrerupere la


vechile lor valori, memorate în zona de date a programului rezident instalat, după
care este eliberată memoria ocupată de programul rezident:

mov ds,es:int8_veche_seg
mov dx,es:int8_veche_off
mov ax,2508h
int 21h ;restaurare int. 8
mov ds,es:int10h_veche_seg
mov dx,es:int10h_veche_off
mov ax,2510h
int 21h ;restaurare int. 10h
mov ds,es:int13h_veche_seg
mov dx,es:int13h_veche_off
mov ax,2513h
int 21h ;restaurare int. 13h
mov ax,0
mov ds,ax
mov dx,0
mov ax,2588h
int 21h ;dezactivare int. 88h
mov ah,49h
int 21h ;eliberare memorie TSR

Modulul de dezinstalare se încheie prin apelul funcţiei DOS 4Ch.


740

23.9 Concluzii

Din conţinutul acestui capitol se poate constata faptul că realizarea de


programe rezidente ridică probleme deosebite faţă de programele clasice. Se
remarcă faptul că este necesară respectarea riguroasă a unor reguli suplimentare de
programare, precum şi luarea în considerare a mai multor detalii tehnice privind
sistemul de calcul şi sistemul de operare. În felul acesta programatorul are mai
puţină libertate de acţiune, iar programul rezultat este mult mai strâns legat de
mediul de operare pentru care a fost creat.
Programele rezidente sunt programe cu grad foarte redus de portabilitate.
Ele pot rula doar pe acea clasă de maşini pentru care au fost realizate (în cazul de
faţă este vorba de maşini de tip PC), ce folosesc una din versiunile sistemului de
operare DOS. De asemenea, ele funcţionează doar în modul real al procesorului,
specific pentru funcţionarea sistemului de operare DOS. Mai mult decât atât, este
posibil ca domeniul de funcţionalitate al unui anumit program rezident să fie şi mai
restrâns dacă acesta se foloseşte de caracteristici particulare ale unei anumite
versiuni de sistem de operare sau de detalii tehnice specifice unei anumite maşini.
O altă menţiune ce trebuie făcută este aceea că nu orice program rezident
funcţionează în bune condiţii, concomitent cu oricare alt program, fie el rezident
sau nu. Acest lucru se datorează faptului că sistemul de operare DOS este
monotasking şi nu a fost prevăzut să trateze conflictele ce pot apare între două
procese active, care solicită aceleaşi resurse. Conflictele apar, de regulă, între un
program rezident şi un alt program, rezident sau nu, care se foloseşte de anumite
particularităţi tehnice, ocolind interfaţa standard oferită de sistemul de întreruperi.
La realizarea programelor rezidente este nevoie să se acorde o mai mare
atenţie optimizării codului, deoarece, pe de o parte, un program rezident trebuie să
ocupe un minimum de memorie internă, iar pe de altă parte, rutinele programului
rezident trebuie să aibă timpi minimi de execuţie pentru a nu încărca prea mult
procesorul cu operaţii suplimentare. Această problemă se pune cu atât mai mult
atunci când programul rezident intervine asupra unor rutine critice ale sistemului.
Efortul suplimentar depus în realizarea programelor rezidente este justificat
deoarece aceste programe oferă o îmbunătăţire a utilizării unor programe cu
frecvenţă mare de utilizare, în condiţii de utilizare redusă a resurselor
calculatorului, precum şi posibilitatea de a realiza lucruri imposibil sau foarte
dificil de obţinut cu ajutorul programelor clasice.

24
741

PROGRAMARE IN MODUL PROTEJAT


24.1 Moduri de operare ale procesoarelor 80x86

Existenţa celor trei moduri de lucru a procesoarelor 80x86: modul real,


modul protejat şi modul virtual 8086, se datorează cerinţei de păstrare a
compatibilităţii în rularea programelor de către procesoarele mai noi faţă de
primele procesoare ale familiei. Astfel procesoarele 8086, 8088 şi 80186 rulau doar
în mod real. Începând de la procesorul 80286 s-a introdus şi modul de operare
protejat, pentru ca procesoarele 80386 şi 80486 să ruleze nativ în mod protejat cu
facilităţi extinse de memorie virtuală, multitasking şi protecţie a spaţiului de adrese,
păstrându-se pentru acestea şi modul real de operare. De asemenea, începând cu
aceste procesoare s-a mai introdus un mod de funcţionare: modul virtual 8086.
Cele trei moduri de operare se caracterizează prin:
 Modul protejat – în acest mod sunt disponibile toate facilităţile oferite de
procesoarele 80386/80486: posibilitatea extinderii memoriei fizice prin
alocarea de memorie virtuală pe hard-disk, sistem multitasking implementat
hardware, protejarea contextului de execuţie al unui program de acţiunile
celorlalte programe care se execută pe sistem.
 Modul real – este modul de lucru nativ al procesoarelor 8086,8088 şi
80186. În acest mod de lucru memoria disponibilă pentru un program este
limitată la 1Mb, instrucţiunile folosesc regiştri pe 16 biţi, modul de adresare
a memoriei este bazat pe segmentare iar multitasking-ul nu este suportat de
hardware. Procesoarele 80386/80486 pot emula acest mod de lucru pentru a
permite programelor scrise în acest fel să ruleze. Pentru aceste procesoare,
modul real este independent de modul protejat. La pornirea calculatoarelor
echipate cu procesoare i386/i486 acestea intră în mod real deoarece
programele BIOS sunt scrise pentru acest mod de lucru. Comutarea între
modul real şi modul protejat se face explicit, procesoarele i386/i486
neputând lucra simultan în aceste două moduri.
 Modul virtual 8086 – reprezintă tot o emulare a procesoarelor 8086, dar sub
modul protejat. Astfel, procesoarele i386/i486, aflate în mod de lucru
protejat, pot simula maşini virtuale 8086. Toate facilităţile modului protejat
sunt disponibile şi sub modul virtual 8086. Un program scris să ruleze sub
modul virtual 8086 este identic cu un program scris pentru un procesor
8086, dar este posibil multitasking-ul controlat hardware. Pentru a se realiza
acest lucru este necesar să existe un program care să monitorizeze execuţia
în modul virtual 8086 pentru iniţializare şi tratarea excepţiilor în stilul
modului protejat.
24.2 Regiştrii procesoarelor i386/i486

Majoritatea regiştrilor procesoarelor i386/i486 sunt pe 32 de biţi, astfel


încât regiştrii procesoarelor din generaţiile anterioare (8086, 80186, 80286) sunt
742

subseturi ai acestora. Câţiva regiştri sunt accesaţi direct prin nume, ceilalţi fiind
setaţi şi citiţi prin instrucţiuni specifice. În continuare sunt reluate unele elemente
privind resursele sistemelor de calcul (regiştri, indicatori de stare) dar în contextul
lucrului protejat.
Regiştrii de segment

Adresele de segment ale modului real şi selectorul de segment din modul


protejat sunt stocate în 6 regiştri pe 16 biţi. Registrul selector de segment este
folosit în modul protejat pentru a selecta descriptorul de segment definit în tabela
descriptorilor.

15 0
CS
DS
ES
SS
FS
GS

Figura 24.1 – Regiştrii de segment

Regiştrii de segment se iniţializează cu următoarele valori:


 Registrul CS – adresa segmentului de cod sau selectorul segmentului de
cod curent.
 Registrul SS – adresa segmentului de stivă sau selectorul segmentului
de stivă curent.
 Regiştrii DS, ES, FS, GS – adresa segmentelor de date sau selectorii
segmentelor de date ai programului.
Registrul “Instruction Pointer”

Registrul pe 32 de biţi EIP (“Instruction Pointer”) stochează offset-ul


următoarei instrucţiuni de executat relativ la adresa de segment CS. Primii 16 biţi
ai registrului EIP pot fi accesaţi sub numele de IP şi sunt folosiţi pentru adresarea
pe 16 biţi.
Regiştrii de uz general

Regiştrii de uz general pe 32 de biţi sunt numiţi EAX, EBX, ECX, EDX,


ESI, EDI, EBP şi ESP. Primii 16 biţi ai lor pot fi accesaţi sub numele de AX, BX,
CX, DX, SI, DI, BP, şi respectiv SP şi sunt folosiţi de către programele pe 16 biţi
pentru procesoarele din generaţiile inferioare. Octeţii regiştrilor AX, BX, CX, DX
743

pot fi referiţi ca AH, AL, BH, BL, CH, CL, DH şi respectiv DL. Aceşti regiştri sunt
folosiţi pentru manipularea datelor în instrucţiuni.

31 15 7 0
16 8
EAX AH A X AL
EBX BH B X BL
ECX CH C X CL
EDX DH D X DL
ESI SI
EDI DI
EBP BP
ESP SP
EIP IP

Figura 24.2 – Regiştrii de uz general


Registrul EFLAGS

Reprezintă extinderea pe 32 de bit a registrului FLAGS de la procesoarele


8086, primii 12 biţi având aceeaşi funcţie ca la procesoarele pe 16 biţi.

31 17 16 15 7 0
Rezer- V R 0 N IO O D I T S Z 0 A 0 P 1 C
vat
M F T PL F F F F F F F F F

Figura 24.3 – Registrul EFLAGS la procesoarele i386

31 18 17 16 7 0
Rezer- A V R N IO O D I T S Z 0 A 0 P 1 C
vat
C M F T PL F F F F F F F F F

Figura 24.4 – Registrul EFLAGS la procesoarele i486

Semnificaţia biţilor suplimentari este următoarea:


 IOPL (biţii 12-13) (Input/Output Privilege Level) indică prioritatea
execuţiei operaţiilor de intrare/ieşire. Această prioritate poate lua valori
744

între 0 şi 3, valoarea 0 semnificând cel mai înalt nivel de prioritate iar


valoarea 3 nivelul cel mai scăzut.
 NT (bitul 14) (Nested Flag) dacă este setat task-ul curent este încapsulat
într-un alt task
 RF (bitul 16) (Resume Flag) este folosit împreună cu instrucţiunea de
breakpoint pentru excepţii. Dacă este setat interzice tratarea de excepţii
multiple pentru o instrucţiune.
 VM (bitul 17) (Virtual-8086 mode) când este setat procesorul intră în
modul virtual 8086. Acest bit poate fi setat doar de o instrucţiune IRET
sau prin permutarea între task-uri în modul protejat.
Pentru procesoarele i486 se mai defineşte un flag:
 AC (bitul 18) (Alignment Check) forţează verificarea alinierii datelor.
Procesorul i486 generează o eroare de aliniament cu cod de eroare 0 în
momentul în care întâlneşte date incorect aliniate. Doar programele care
rulează cu prioritatea 3 pot genera eroare de aliniament. Acest bit este
activat de bitul AM definit în registrul CR0.
Regiştrii de control
Procesoarele i386/i486 au trei regiştri de control: CR0, CR2 şi CR3,
registrul CR1 este rezervat pentru utilizări viitoare. Aceşti trei regiştri stochează
starea globală sau controlul operaţiilor procesoarelor i386/i486.
Formatul registrului de control al maşinii (CR0) este dat în figura 24.5

31 4 3 2 1 0
PG Rezervat ET TS EM MP PE

Figura 24.5 – Registrul CR0 al procesorului i386

31 30 29 18 16 5 4 3 2 1 0
PG CD NW AM WP NE ET TS EM MP PE

Figura 24.6 – Registrul CR0 al procesorului i486

Semnificaţia biţilor este următoarea:


 PG (bitul 31) (Paging Enable) când este setat permite folosirea
modelului de memorie bazat pe pagini.
 ET (bitul 4) (Processor Extension Type) când este setat se foloseşte
modul de operare pe 32 de biţi compatibil 80387, altfel este folosit
745

modul pe 16 biţi compatibil 80287 pentru funcţionarea coprocesorului


matematic. La procesoarele i486 este întotdeauna setat.
 TS (bitul 3) (Task Switched) este automat setat atunci când se comută
între task-uri, pentru a permite coprocesorului să-şi salveze contextul în
timpul comutării.
 EM (bitul 2) (Emulate Coprocessor) când este setat generează excepţia
“coprocesor indisponibil”. Instrucţiunea WAIT ignoră setarea acestui
bit.
 MP (bitul 1) (Math Present) când sunt setaţi biţii TS şi MP, execuţia
instrucţiunii WAIT sau a instrucţiunilor de coprocesor generează
excepţia “coprocesor indisponibil”.
 PE (bitul 0) (Protection Enable) când este setat procesorul funcţionează
în mod protejat, altfel lucrează în mod real.
Procesoarele i486 mai definesc următorii biţi:
 CD (bitul 30) (Cache Disabled) când este setat umplerea memoriei
cache internă a procesorului este dezactivată.
 NW (bitul 29) (Not Write-Through) controlează procesul de
reîmprospătare a memoriei cache, împreună cu bitul CD.
 AM (bitul 18) (Alignment Mask) controlează verificarea alinierii. Când
este setat şi prioritatea este 3, procesorul verifică alinierea potrivit cu
valoarea bitului AC definit în EFLAGS.
 WP (bitul 16) (Write Protect) când memoria este organizată la nivel de
pagini, procesele aflate pe nivele prioritare pot citi şi scrie pagini aflate
pe nivele neprioritare. La procesoarele i486, dacă acest bit este setat,
paginile nivelelor neprioritare sunt read-only.
 NE (bitul 5) (Numeric Error) datorită faptului că procesorul i486
conţine coprocesor matematic, poate efectua operaţii în virgulă mobilă.
Când acest bit este setat procesorul generează o eroare nemascată prin
excepţia 16 pentru a reporta o eroare de calcul în virgulă mobilă.
Registrul CR2 (Page Fault Linear Address Register) stochează adresa pe 32
de biţi care a cauzat o eroare de pagină.
Registrul CR3 (Page Directory Base Address Register) conţine adresa
fizică de referinţă a tabelei de pagini, atunci când mecanismul de paginare a
memoriei este activat. Primii 12 biţi ai acestui registru sunt întotdeauna 0 deoarece
dimensiunea unei pagini este de 4K. În cazul procesoarelor i386 aceşti biţi sunt
rezervaţi.
Registrul adreselor de sistem

Procesoarele i386/i486 creează următoarele tabele de sistem atunci când


lucrează în mod protejat:
 GDT (Global Descriptor Table) – Tabela descriptorilor globali
 LDT (Local Descriptor Table) – Tabela descriptorilor locali
746

 IDT (Interrupt Description Table) – Tabela descriptorilor întreruperilor

Regiştrii GDTR şi IDTR stochează adresa liniară şi limita tabelelor CDT şi


IDT. Registrul LDTR stochează selectorul de segment al tabelei LDT curente, iar
partea “invizibilă”, figura 24.7 (aria haşurată din figură stochează adresa de bază şi
limita acestei tabele)
LDTR (selector) Base Register Limit Register

TR (selector) Base Register Limit Register

Figura 24.7 – Utilizarea regiştrilor LDTR şi TR

Registrul proceselor (TR – task register) stochează selectorul de segment al


segmentului de stare (TSS – task state segment) pentru procesul curent. În
segmentul de stare se salvează contextul procesului curent pe timpul comutării între
task-uri. Partea invizibilă a registrului TR stochează adresa de bază şi limita
segmentului de stare a procesului (TSS).
Registrul descriptorului de segment

Pentru fiecare registru de segment există câte un registru de descriptor de


segment. Aceşti descriptori sunt “invizibili” şi nu pot fi accesaţi în nici un fel. Când
se încarcă un selector de segment într-un registru de segment, descriptorul asociat
definit în tabela descriptorilor se încarcă în registrul descriptor asociat. Aceşti
regiştri permit referirea mai rapidă a segmentelor şi contribuie la implementarea
mecanismului de protecţie la memoria segmentului.
Structuri de date ale modului protejat

Mediul de execuţie a programelor în modul protejat diferă semnificativ de


execuţia lor în modul real. Pentru a lansa în execuţie un program în mod protejat
este necesară construirea de structuri de date ale sistemului.
Tabela de descriptori globali, GDT este un vector de descriptori pe 8 octeţi
şi este necesară pentru toate programele care se execută în mod protejat.
Segmentele de date, de cod şi de sistem sunt definite în modul protejat. Fiecare
descriptor de segment are propriul lui format după cum urmează, figura 24.8.

31 16 15 0
I/O Map Base Reserved 64
Reserved LDT 60
Reserved GS 5C
Reserved FS 58
747

Reserved DS 54
Reserved SS 50
Reserved CS 4C
Reserved ES 48
EDI 44
ESI 40
EBP 3C
ESP 38
EBX 34
EDX 30
ECX 2C
EAX 28
EFLAGS 24
EIP 20
CR3 1C
Reserved SS2 18
ESP2 14
Reserved SS1 10
ESP1 0C
Reserved SS0 8
ESP0 4
Reserved Back link to previous TSS 0

Figura 24.8 – Formatul segmentului de stare a proceselor(TSS)

Base G B 0 V Limit P DPL 1 0 E W A Base 4


31..24 19..16 23..16
Segment Base 15..0 Segment Limit 15..0 0
G – Granulaţia B – Segment mare
V – Disponibil pentru utilizare P – Existenţa segmentului
DPL – Nivelul de prioritate al E – Expandare
descriptorului A – Accesat
W – Permite scriere

Figura 24.9 – Formatul descriptorului segmentului de date

Base G D 0 V Limit P DPL 1 1 C R A Base 4


31..24 19..16 23..16
Segment Base 15..0 Segment Limit 15..0 0
748

D – dimensiunea implicită a R – permite citirea


operaţiilor C – bit de conformitate

Figura 24.10 – Formatul descriptorului segmentului de cod

Rezervat P DPL 0 0 1 0 1 Rezervat 4


Segment Base 15..0 Segment Limit 15..0 0

Figura 24.11 – Formatul descriptorului “Task Gate”

Offsetul în segment 31..16 P DPL 0 X 1 0 0 0 0 0 Count 4


Segment Base 15..0 Segment Limit 15..0 0

Figura 24.12 – Formatul descriptorului “Call Gate”

Offsetul în segment 31..16 P DPL 0 X 1 1 1 T 0 0 Rezerved 4


Segment Base 15..0 Segment Limit 15..0 0

T – 0 : “interrupt gate”, 1: “trap gate”

Figura 24.12 – Formatul descriptorului “Interrupt/Trap Gate”

Base G 0 0 V Limit P DPL 0 0 0 1 0 Base 4


31..24 19..16 23..16
Segment Base 15..0 Segment Limit 15..0 0

Figura 24.13 – Formatul descriptorului LDT

Base G 0 0 V Limit P DPL 0 X 0 B 1 Base 4


31..24 19..16 23..16
Segment Base 15..0 Segment Limit 15..0 0

X – 0: procesor 80286, 1:procesor i386


B – busy

Figura 24.14 – Formatul descriptorului segmentului sistem


Instrucţiuni

Procesoarele i486 suportă toate instrucţiunile procesorului i386, punând la


dispoziţie încă şase instrucţiuni.
8

Instrucţiuni utilizator

 BSWAP (Byte Swap) această instrucţiune inversează ordinea datelor


stocate într-un registru pe 32 biţi.
 XADD (Exchange and Add) această instrucţiune încarcă datele din
operandul destinaţie în operandul sursă, calculează suma între valoarea
originală a operandului sursă şi operandul destinaţie şi o stochează în
operandul destinaţie.
 CMPXCHG (Compare and Exchange) această instrucţiune compară
datele stocate în acumulator cu operandul destinaţie, dacă sunt egale
încarcă operandul sursă în operandul destinaţie, altfel încarcă datele din
acumulator în operandul destinaţie.

Instrucţiuni de sistem

 INVD (Invalidate Cache) această instrucţiune goleşte memoria cache


internă şi trimite semnal către memoria cache externă pentru golire.
 INVLPG (Invalidate TLB Cache) instrucţiunea invalidează o intrare din
TLB.
 WBINVD (Write-Back and Invalidate Cache) această instrucţiune
goleşte memoria cache internă şi trimite semnale de write-back şi de
golire către cea externă.
24.3 Moduri de gestiune a memoriei

Procesoarele i386/i486 suportă două tipuri de gestiune a memoriei;


segmentarea şi paginarea. Ambele mecanisme oferă protecţie pentru un program
împotriva interacţiunilor nedorite cu alte programe. Exemplul clasic este al
sistemului de operare care trebuie protejat împotriva funcţionarii aleatorii a
programelor de aplicaţie.
Prin mecanismul de segmentare, procesorul converteşte adresa logică de tip
segment:offset în adresă liniară. Apoi, prin mecanismul de paginare converteşte
adresa liniară în adresă fizică, atunci când paginarea este activă. Adresa liniară
coincide cu adresa fizică atunci când paginarea nu este activă. Mecanismul de
paginare este folosit pentru extinderea memoriei fizice prin alocarea de memorie
virtuală pe hard-disk.
Mecanismul de segmentare

Segmentarea oferă o modalitate simplă dar nestructurată de gestiune a


memoriei. Un program poate avea câteva spaţii de adresă protejate independente.
Un segment este un bloc de memorie a cărei dimensiune este variabilă.
Dimensiunea segmentelor folosite de către un program poate fi oricât de mare, cu
condiţia să nu depăşească memoria totală disponibilă în sistem. Înainte de a accesa
9

un segment trebuie setate adresa liniară de bază a segmentului, dimensiunea, tipul


segmentului şi atributele acestuia.
Atunci când procesorul accesează un segment, verifică definirea acestuia
din descriptorul de segment pentru a asigura faptul că accesul nu încalcă regulile de
definire ale segmentului. Această procedură previne interacţiunea dintre segmente
sau programe diferite. Politica de alocare a segmentelor depinde de sistemul de
operare care rulează pe maşină. Mai multe segmente diferite pot împărţi acelaşi
bloc de segment prin definirea descriptoarelor cu aceeaşi adresă de bază şi aceeaşi
dimensiune.
Mecanismul de translatare a adresei la segmentare
În modul protejat selectorul de segment punctează către descriptorul de
segment. Fiecare selector de segment trebuie să aibă o intrare în tabela
descriptorilor(fie în cea globală – GDT, fie în cea locală – LDT). Fiecare referinţă
de memorie este asociată cu un selector de segment. Descriptorul de segment
trebuie să includă toate informaţiile despre segment. Procesorul i386/i486 obţine
adresa liniară de bază din descriptorul corespunzător segmentului la care adaugă
deplasamentul pentru a forma o adresă liniara pe 32 de biţi. În acelaşi timp
procesorul verifică tipul segmentului, existenţa acestuia şi dimensiunea
segmentului. Se generează o excepţie în cazul în care referinţa încalcă definiţia din
descriptor, de exemplu, dacă deplasamentul este dincolo de limita segmentului.
Limita maximă pentru fiecare segment este de 1Gb (dacă bitul granulaţie este setat
pe valoarea 0) sau 4 Gb (dacă bitul granulaţie este setat pe valoarea 1).
Cei mai puţin semnificativi biţi ai selectorului de segment sunt consideraţi
cu valoarea 0 în timpul indexării în tabela descriptorilor. Procesorul interpretează
aceşti trei biţi, figura 24.15.

15 3 2 1 0
TI RPL

TI – indicator de tabelă
RPL (Requested Privilege Level) – nivelul de prioritate solicitat

Figura 24.15 –Selectorul de segment

Dacă TI = 0 descriptorul se selectează din GDT, dacă TI = 1 descriptorul se


selectează din LDT.
Paginarea

Spre deosebire de segmentare, paginarea împarte memoria sistemului în


blocuri de memorie de dimensiune fixă, fiecare bloc fiind numit pagină. Pe
sistemele i386/i486, o pagină ocupa 4Kb. O adresă liniară, obţinută prin
10

translatarea la segmentare dintr-o adresă utilizator de tip segment:offset, este


translatată la adresa fizică corespunzătoare prin mecanismul de traslatare a adresei
la paginare, atunci când paginarea este activată.
Prin mecanismul de translatare, o parte a capacităţii de stocare a hard disk-
ului poate fi tratată ca spaţiu de adrese de memorie. Atunci când procesorul trebuie
să acceseze o pagină care nu se află în memoria fizică, generează o excepţie pentru
a permite sistemului de operare să încarce pagina cerută, apoi reia execuţia din
punctul de întrerupere.
24.4 Comutarea în modul protejat
Intrarea în modul protejat
La pornirea unui sistem i386 sau i486 programul de boot se rulează în mod
real. Pentru a rula programe în mod protejat trebuie executate câteva proceduri
pentru a comuta sistemul din mod real în mod protejat. Aceste proceduri se rulează
în mod real şi trebuie să iniţializeze structurile de date şi regiştrii specifici modului
protejat.
Cea mai importantă structură care trebuie iniţializată este tabela
descriptorilor globali (GDT). Această structură trebuie să aibă definite cel puţin un
descriptor de segment pentru segmentul de cod şi un descriptor de segment pentru
segmentul de date pentru a permite accesarea instrucţiunilor şi a datelor conform cu
regulile din modul protejat. De asemenea adresa de bază şi limita de adresă a
tabelei GDT trebuie încărcate în registrul GDTR înainte de intrarea în modul
protejat.
Dacă se doreşte lucrul cu vectori de întreruperi mai trebuie construită şi
încărcată tabela descriptorilor de întreruperi (IDT). În acest caz adresa de bază şi
limita de memorie a tabelei IDT se încarcă în registrul IDTR.
După setarea acestor structuri se poate intra în modul protejat prin setarea
bitului PE în registrul CR0. După setarea acestui bit se execută o instrucţiune de
salt necondiţionat pentru golirea cozii de instrucţiuni.
La intrarea în modul protejat toţi regiştrii de segment conţin valori setate în
modul real. De aceea programul trebuie să reîncarce selectorul de segment cu
regiştrii de segment. În acest moment programul a comutat în modul protejat cu
nivelul de prioritate 0.
Ieşirea din modul protejat

Procesorul i386 poate reintra în modul real prin setarea bit-ului PE al


registrului CR0 la valoarea 0. Pentru a continua execuţia programului
în mod real anumite structuri de date ale sistemului trebuie resetate
pentru a permite programului să ruleze în mediul de execuţie al
modului real. Pentru reintrarea în modul real se execută următorii paşi:
 Se setează prioritatea de execuţie a procesului curent la valoarea 0 –
prioritatea modului real.
11

 Se schimbă limita segmentului CS la valoarea 0ffffh prin transferarea


controlului către un segment de cod cu limita 0ffffh – limita
segmentului de cod CS în mod real.
 Se încarcă toţi regiştrii de segment cu excepţia registrului CS cu un
selector de segment pentru care descriptorul este de forma: DPL = 0,
writable = 1, expand = 0, present = 1, granular = 0, big = 0 şi limit =
0ffffh.
 Se dezactivează întreruperile.
 Se setează bitul PE din registrul CR0 la valoarea 0.
 Se execută o instrucţiune de salt necondiţionat pentru a goli coada de
instrucţiuni.
 Se setează registrul IDTR pentru a puncta către tabela vectorilor de
întrerupere a modului real.
 Se activează întreruperile.
 Programul se află acum în mod real. Dacă este nevoie se reiniţializează
regiştrii de segment în modul real.
Descriptorii segmentelor de cod şi de date pentru modul real sunt:

;formatul descriptorului segmentului de cod de nivel 0

code_sel dw 0ffffh ;limita(0-15)


dw ? ;adresa de baza(0-15)
db ? ;adresa de baza(16-23)
db 9ah ;present=1,readable=1
db 0 ;limit a(16-19), G=0, D=0
db 0 ;baza(24-31)

;formatul descriptorului segmentului de date

dmy_selec dw 0ffffh ;limita(0-15)


dw ? ;adresa de baza(0-15)
db ? ;adresa de baza(16-23)
db 92h ;present=1, writable=1
db 0 ;limita(16-19), G=0, D=0
db 0 ;baza(24-31)

Reintrarea în modul real se realizează astfel :

;program în modul protejat executat cu prioritatea 0


db 0eah ;salt neconditionat
dw offset real_l ;ip
dw code_sel ;selector cs
real_l:
mov ax,dmy_selec ;selector fictiv
mov es,ax ;resetarea registrilor de segment
mov ds,ax
12
mov fs,ax
mov gs,ax
mov ss,ax
cli ;dezactivarea intreruperilor
mov eax,cr0
and eax,not prot_enable ;dezactivarea modului protejat
mov cr0,eax
db 0eah ;salt neconditionat
dw offset real_mode ;EIP
dw code ;CS

read_mode:
mov ax,data
mov ds,ax
lidt [oldidt] ;reincarcarea tabelei de
;intreruperi pentru modul real
sti ;reactivarea intreruperilor

Exemplu
Următorul exemplu demonstrează comutarea între mod real şi modul
protejat. Acest program pregăteşte structurile de date de bază pentru intrarea în
modul protejat. Programul nu include paginare, multitasking sau protecţie în modul
protejat.
În cadrul acestui program se folosesc două fişiere incluse:
 STRUCT –defineşte majoritatea structurilor de date folosite în modul
protejat
 MACRO1 –defineşte câteva macrodefiniţii folosite în programul
principal.

Fişierul STRUCT:

;Structura descriptorului de segment

dscp struct
D_lim1 dw 0
D_base1 dw 0
D_base2 db 0
D_type db 0
D_lim2 db 0
D_base3 db 0
dscp ends

;Structura stivei dupa intreruperea procesului în mod real

stkdef struct
oldeip dw 0
dw 0
13
oldcs dw 0
dw 0
oldflg dw 0
dw 0
oldsp dw 0
dw 0
oldss dw 0
dw 0
oldes dw 0
dw 0
oldds dw 0
dw 0
oldfs dw 0
dw 0
oldgs dw 0
dw 0
stkdef ends

;Structura tabelei de pagini

page_tbl struc
pg_stat db ?
pg_avail db ?
pg_limit db ?
page_tbl ends

Fişierul MACRO1

;Macrodefinitie pentru definirea stivei în TSS

TSS_stack macro ss0,esp0,ss1,esp1,ss2,esp2


dd 0
dd offset esp0
dd ss0
dd offset esp1
dd ss1
dd offset esp2
dd ss2
endm

;Macrodefinitie pentru definirea CR3 în TSS

TSS_cr3 macro
dd 0
endm

;Macrodefinitie pentru definirea registrilor generali în TSS

TSS_regs macro teip,tflg,teax,tebx,tecx,tedx,tesi,tedi,tebp,tesp


dd offset teip
dd tflg
14
dd teax
dd tecx
dd tedx
dd tebx
dd offset tesp
dd tebp
dd tesi
dd tedi
endm

;Macrodefinitie pentru definirea registrilor de segment în TSS

TSS_seg macro tes,tcs,tss,tds,tfs,tgs


dd tes
dd tcs
dd tss
dd tds
dd tfs
dd tgs
endm

;Macrodefinitie pentru salt far neconditionat

callf macro selector


db 9ah
dw 0
dw selector
endm

Program: EN.ASM

Acest program demonstrează trecerea din modul real al sistemului de


operare DOS în modul protejat şi reîntoarcerea din modul protejat în modul real
DOS. Programul setează, mai întâi, structurile de date ale modului protejat (GDT,
IDT şi TSS) apoi intră în modul protejat, unde afişează un mesaj, după care
părăseşte modul protejat şi restaurează contextul modului real. Paşii de execuţie ai
programului sunt:

Pasul 0: Definirea EQU.

Pasul 1: Definirea tabelei descriptorilor globali (GDT).

Tabela descriptorilor locali nu este folosită în acest program, astfel încât toţi
descriptorii sunt definiţi în tabela descriptorilor globali. În această tabelă se
defineşte doar dimensiunea şi tipul fiecărui descriptor, adresa de bază urmând a fi
setată la momentul execuţiei.
15

 Primul descriptor trebuie să fie un descriptor NULL.


 Descriptorul segmentului video, neiniţializat la momentul execuţiei, are
adresa de bază B8000h.

Pasul 2: Definirea tabelei de întreruperi(IDT).

În acest program se definesc 21 de întreruperi în IDT. Selectorul de


segment a acestor întreruperi este int_selec. Offset-ul întreruperilor este iniţializat
la momentul execuţiei.

Pasul 3: Definirea variabilelor.

 pGDT este un pointer către o structură de şase octeţi conţinând adresa


de bază şi dimensiunea GDT.
 pIDT este un pointer către o structură de şase octeţi conţinând adresa de
bază şi dimensiunea IDT.
 pold este un pointer către o structură de şase octeţi conţinând adresa de
bază şi dimensiunea tabelei vectorilor de întrerupere definiţi în modul
real DOS.

Pasul 4: Definirea tabelei de mapare pentru fiecare selector către


segmentul corespunzător.

Această tabelă conţine acei selectori pentru care adresa de bază trebuie
iniţializată în descriptor. Segmentul corespunzător este definit folosind selectorul
asociat.
 gdt_tab-size conţine numărul de intrări ale tabelei GDT.

Pasul 5: Definirea mesajelor.

Pasul 6: Definirea segmentelor de stivă de nivel 0, 1 şi 2.

Pasul 7: Setarea TSS.

Pasul 8: Definirea unui segment fictiv folosit pentru obţinerea valorii


registrului descriptor de segment la întoarcerea în modul real.

Pasul 9: Iniţializarea punctului de intrare pentru fiecare descriptor de


întrerupere definit în IDT. Pentru fiecare rutină de întrerupere se rezervă 4 octeţi
într-un vector în memorie.
16

Pasul 10: Programul obţine adresa liniară de bază (32 de biţi) şi


dimensiunea pentru GDT şi IDT şi le stochează.

Pasul 11: Pe baza adresei de segment definită în tabela gdt_phys_tab obţine


adresa liniară de bază şi o setează în descriptorul corespunzător selectorului de
segment.

Pasul 12: Comută în modul protejat:

 Încarcă adresa liniară de bază şi dimensiunea GDT în registrul GDTR


 Încarcă adresa liniară de bază şi dimensiunea IDT în registrul IDTR.
 Setează bitul PF în registrul CR0.
 Apelează o instrucţiune de salt necondiţionat pentru a goli coada de
instrucţiuni.

Pasul 13: Setează regiştrii LDTR, SS, SP, DS, ES, FS şi GS.

Pasul 14: Afişează mesajul pe ecran.

Datorită faptului că funcţiile de sistem DOS nu sunt disponibile în modul


protejat, programul scrie mesajul direct în memoria video, în secvenţa:
 scrie spaţii în memoria video pentru a şterge ecranul
 afişează mesajul

Pasul 15: Încarcă selectorul segmentului procesului curent în registrul


proceselor (TS)

Pasul 16: Apelează o întrerupere software prin intermediul descriptorilor de


întreruperi definiţi în IDT. Rutina întreruperii afişează numărul întreruperii şi
comută înapoi în modul real DOS.

Fişierul EN.ASM

.386p
include struct
include macro1

;Pasul 0: definire EQU

INTNO equ 21
DSCPSIZE equ 8
INTSIZE equ 4
TWO equ 2
prot_enable equ 01h
17
attribute equ 07h
space equ 20h

;Pasul 1: GDT

GDT segment para public use16 ‘GDT’


gdt_tab label qword

null_selec equ $-gdt_tab


dscp <,,,,,>

code_selec equ $-gdt_tab ;descriptorul selectorul


;segmentului de
dscp <0ffffh,,,09h,,> ;cod

task0_TSS_selec equ $-gdt_tab ;descriptorul selectorului


dscp <task0_TSS_limit,,,089h,,> ;segmentului TSS

stk0_selec equ $-gdt_tab ;descriptorul selectorului


dscp <stk0_limit,,,92h,,> ;segmentului de stiva de nivel
0

stk1_selec equ $-gdt_tab or 1 ;descriptorul selectorului


dscp <stk1_limit,,,0b2h,,> ;segmentului de stiva de
;nivel 1

stk2_selec equ $-gdt_tab or 2 ;descriptorul selectorului


dscp <stk2_limit,,,0d2h,,> ;segmentului de stiva de
;nivel 2

dmy_selec equ $-gdt_tab


dscp <0ffffh,,,92h,,>

video_selec equ $-gdt_tab or 3


dscp <0ffffh,8000h,0bh,0f2h,,>

gdata_selec equ $-gdt_tab


dscp <gdata_limit,,,0f2h,,>

int_selec equ $-gdt_tab


dscp <0ffffh,,,09ah,,>

gdt_limit equ $-gdt_tab


GDT ends

;Pasul 2: IDT

IDT segment para public use 16 ‘idt’


idt_tab equ $
REPT INTNO
dscop <,int_selec,0,0eeh,,>
ENDM
18
idt_limit equ $
IDT ends

;Segmentul de date

Gdata segment para public use16 ‘Gdata’

;Pasul 3: Definirea variabilelor

pGDT label fword


pGDT_limit dw ?
pGDT_addr dd ?

pIDT label fword


pIDT_limit dw ?
pIDT_addr dd ?

pold label fword


dIDT_limit dw 03ffh
dIDT_addr dd 0

;Pasul 4: tabela maparilor intre descriptori şi segmente

gdt_phys_tab label word


dw task0_TSS_selec
dw task0_TSS
dw stk0_selec
dw stk0
dw stk1_selec
dw stk1
dw stk2_selec
dw stk2
dw dmy_selec
dw dmy
dw code_selec
dw code
dw gdata_selec
dw gdata
dw int_selec
dw code
gdt_tab_size equ ($ - gdt_phys_tab) / 4

;Pasul 5: Definirea mesajelor

in_protected db ‘MOD PROTEJAT’,0


int_msg db ‘Intreruperea:’
int_num dw ?
db ‘H’,0

Gdata_limit equ $
Gdata ends
19
;Pasul 6: Definirea segmentelor de stiva de nivel 0, 1 şi 2

stk0 segment para public use16 ‘stk0’


db 100h dup(0)
stk0_limit equ $
stk0 ends

stk1 segment para public use16 ‘stk1’


db 100h dup(0)
stk1_limit equ $
stk1 ends

stk2 segment para public use16 ‘stk2’


db 100h dup(0)
stk2_limit equ $
stk2 ends

;Pasul 7: TSS

task0_TSS segment para public use16 ‘task0’


TSS_stack stk0_selec,stk0_limit,stk1_selec,
stk1_limit,stk2_selec,stk2_limit
TSS_cr3 0
TSS_regs 0,0,0,0,0,0,0,0,0,stk0_limit
TSS_seg gdata_selec,code_selec,stk0_selec,
gdata_selec,gdata_selec,gdata_selec
dd 0 ;LDT
dw 0 ;task trap flag
dw 68h ;adresa de I/O
task0_TSS_limit equ $
task0_TSS ends

;Pasul 8: Segmentul fictiv

dmy segment para public use16 ‘dmy’


db 128 dup(0)
dmy ends

;Segmentul de cod

code segment para public use16 ‘code’


assume cs:code,ds:gdata

main proc far


mov ax,gdata
mov ds,ax

;Pasul 9: Initializarea IDT

mov ax,IDT
mov es,ax
mov di,offset idt_tab
20
mov ax,offset int_entry

mov cx,INTNO
fillidt:
mov es:[di],ax
add di,DSCPSIZE
axx ax,INTSIZE
loop fillidt

;Pasul 10: Obtinerea adresei şi dimensiunii GDT/IDT

mov ax,offset gdt_kimit


mov pGDT_limit,ax
xor eax,eax
mov ax,GDT
shl eax,4

mov pGDT_addr,eax

mov ax,offset idt_limit


mov pIDT_limit,ax
xor eax.eax
mov ax,idt
shl eax,4

mov pIDT_addr,eax

;Pasul 11:Pe baza gdt_phys_tab se seteaza adresa


;de baza pentru fiecare descriptor

mov ax,GDT
mov es,ax
mov si,offset gdt_phys_tab

mov cx,gdt_tab_size
bdt1:
lodsw
mov bx,ax
and bx,0fff8h
lodsw

push ax
shl ax,4
mov es:[bx][d_base1],ax

pop ax
shr ax,12
mov es:[bx],d_base2],al

loop bdt1

;Pasul 12: comutarea în mod protejat


21

cli
lgdt [pGDT]
lidt [pIDT]

mov eax,cr0
or al,prot_enable
mov cr0,eax

jmp dword ptr cs:[enter_prot]

enter_prot:
dw offset now_in_prot
dw code_selec

;Pasul 13: Executie în mod protejat


; - setare LDTR,SS,SP,DS,ES,FS,GS

now_in_prot:

xor ax,ax
lidt ax
mov ax,stk0_selec
mov ss,ax
mov sp,offset stk0_limit
mov ax,gdata_selec
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax

;Pasul 14: Afisarea mesajului

mov ax,video_selec
mov es,ax
mov cx,4000h
xor di,di
mov ah,attribute
mov al,space
rep stosw
mov si,offset in_protected

mov di,320
call disp_it

;Pasul 15: Incarcarea TSS în registrul TR

mov ax,task0_TSS_selec
ltr ax
22
;Pasul 16: Intoarcerea în modul real DOS

int 20

int_entry:
REPT INTNO
call disp
iret
ENDM

disp:
pop ax
mov bx,gdata_selec
mov ds,bx
sub ax,offset int_entry

shr ax,TWO
mov si,offset int_num
mov cx,TWO
call htoa
mov si,offset int_msg
mov di,5*160
call disp_it

cli
mov ax,dmy_selec
mov es,ax
mov ds,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
and eax,not prot_enable
mov cr0,eax

db 0eah
dw offset next_instruction
dw code

next_instruction:
mov ax,Gdata
mov ds,ax
mov ax,stk0
mov ss,ax
mov sp,offset stk0_limit

lidt [pold]

sti

mov ax,4c00h
23
int 21h
main endp

disp_it proc near


mov ax,video_selec
mov es,ax
mov ah,attribute
disp_itl:
lodsb
stosw
cmp al,0
jne disp_itl
ret
disp_it endp

htoa_tab db ‘0123456789ABCDEF’
htoa proc near
xor ebx,ebx
add si,cx
dec si
htoal:
mov bl,al
and bl,0fh
mov bl,cs:[htoa_tab][ebx]

mov byte ptr [esi],bl


dec esi
shr eax,4
loop htoal
ret
htoa endp

code ends

end main
24.5 Implementarea modului de lucru multitasking

În sistemele multitasking, este necesară salvarea stării maşinii în timpul


comutării între procese. Procesoarele i386/i486 suportă această operaţie în mod
nativ, putând comuta rapid între două procese. Atunci când este solicitată
comutarea între procese, procesorul salvează starea procesului curent în segmentul
de stare a procesului (TSS), încarcă contextul noului proces din segmentul său de
stare, verifică respectarea integrităţii datelor folosite de acesta şi începe execuţia
noului proces.
Comutarea între procese poate fi invocata printr-o instrucţiune de tip CALL
sau JMP către un descriptor de segment TSS sau către un descriptor de tip “Task
Gate”. De asemenea mai poate fi invocată prin intermediul unei întreruperi prin
iniţializarea unui element din IDT cu descriptorul unui proces. Un descriptor de
24

segment TSS poate fi stocat doar în GDT. Dacă este invocată o comutare către un
proces al cărui descriptor este stocat în LDT, procesorul generează o excepţie.
Dacă a fost invocată o comutare prin intermediul unei instrucţiuni CALL sau
INT, întoarcerea către procesul iniţial se realizează printr-o instrucţiune IRET.
Exemplul care urmează realizează comutarea între două procese numite:
“task0” şi ”task1” folosind o instrucţiune JMP către descriptorul TSS al procesului
“task1”. După ce se realizează comutarea, procesul “task1” realizează întoarcerea
către procesul “task0” folosind o întrerupere software. Această întrerupere are
descriptorul definit în IDT fiind ceea ce se numeşte un descriptor de tip “task gate”
către descriptorul TSS al procesului “task0”.

Program: mult.asm

Mai întâi se definesc cele două procese:

TSS0 dd 0 ;
dd 0,0 ;esp0,ss0
dd 0,0 ;esp1,ss1
dd 0,0 ;esp2,ss2
dd 0 ;cr3
dd 0 ;eip
dd 0 ;eflags
dd 0,0,0,0,0,0,0,0 ;eax,ecx,ebx,edx,esp,ebp,esi,edi
dd 0,0,0,0,0,0 ;es,cs,ss,ds,fs,gs
dd 0 ;LDT
TSS0_limit equ $

TSS1 dd 0
dd task1_esp0,task1_ss0 ;esp0,ss0
dd task1_esp1,task1_ss1 ;esp1,ss1
dd task1_esp2,task1_ss2 ;esp2,ss2
dd 0 ;cr3
dd task1_eip ;eip
dd task1_eflags ;eflags
dd task1_eax,task1_ecx,task1_ebx,task1_edx
;eax,ecx,ebx,edx
dd task1_esp,task1_ebp,task1_esi,task1_edi
;esp,ebp,esi,edi
dd task1_es,task1_cs,task1_ss,task1_ds ;es,cs,ss
dd task1_ds,task1_fs,task1_gs ;ds,fs,gs
dd 0
TSS1_limit equ $

Apoi se definesc descriptorii pentru cele două procese:

task0_TSS_selec label word


dw TSS0_limit
dw TSS0_base1
25
db TSS0_base2
db 89h
db 0
db 0

task1_TSS_selec label word


dw TSS1_limit
dw TSS1_base1
db TSS1_base2
db 89h
db 0
db 0

Faţă de exemplul precedent mai intervin următorii paşi suplimentari:

Pasul 1.1 Definirea descriptorului, selectorului segmentului de cod şi


TSS pentru procesul “task1”:

task1_TSS_selec equ $-gdt_tab or 1


dscp <task1_TSS_limit,,,09ah,,>
task1_code_selec equ $-gdt_tab or 1
dscp <task1_seg_limit,,,0bah,,>

Pasul 2.1 Definirea descriptorului “task gate” în IDT, punctând către


TSS-ul procesului “task0”:

dscp <,task0_TSS_selec,0,0e5h,,> ;intreruperea 21

Pasul 4.1 Definirea mapării între descriptor şi segment pentru procesul


“task1”

dw task1_TSS_selec
dw task1_TSS
dw task1_code_selec
dw task1_seg

Pasul 7.1 Definirea TSS pentru procesul “task1”.

task1_TSS segment para public use16 ‘task1’


TSS_stack stk0_selec,stk0_limit,stk1_selec,
stk1_limit,stk2_selec,stk2_limit
TSS_cr3 0
TSS_regs task1_entry,2,0,0,0,0,0,0,0,stk1_limit
TSS_seg gdata_selec,task1_code_selec,stk1_selec,
gdata_selec,gdata_selec,gdata_selec
dd 0
dw 0
26
dw 68h
task1_TSS_limit equ $
task1_TSS ends

Pasul 15.1 Comutarea către procesul “task1” prin salt necondiţionat


către selectorul segmentului TSS al procesului “task1”

jmpf task1_TSS_selec

Pasul 17: Segmentul de cod al procesului “task1”

task1_seg segment para public use16 ‘task1_seg’


assume cs:task1_seg,ds:gdata
task1_entry proc near
mov si,offset task1_msg
mov di,160*3
call disp2

int21
task1_entry endp

disp2 proc near


mov ax,video_selec
mov es,ax
mov ah,attribute
disp21:
lodsb
stosw
cmp al,0
jne disp21
ret
disp2 endp

task1_seg_limit equ $
task1_seg ends
24.6 Concluzii

Spre deosebire de modul real, modul protejat oferă programatorului întreaga


capacitate a procesoarelor i386/i486. În modul protejat multitasking-ul, memoria
virtuală şi protejarea spaţiului alocat unui program fiind asigurate în mod nativ,
prin hardware.
În contextul tendinţei de pe piaţa software-ului către aplicaţii care necesită
un volum tot mai mare de resurse, precum şi datorită existenţei sistemelor de
operare care lucrează în mod protejat (sistemele MS Windows) cunoaşterea
principiilor de programare în acest mod este obligatorie pentru realizarea de
programe performante sub aceste platforme.
27

25
PROGRAMAREA APLICAŢIILOR WINDOWS ÎN
LIMBAJ DE ASAMBLARE

25.1 Interfaţa de programare a aplicaţiilor Windows

API (Application Programming Interface) pentru sistemele Win32 este


implementat în biblioteci cu legare dinamică.. Aceste biblioteci sunt separate de
programul executabil. Spre deosebire de DOS, API-urile nu sunt accesate prin
întreruperi.
Codul funcţiilor Windows este stocat în fişiere din afara
programului, numite DLL (Dynamic Link Libraries) - biblioteci cu
legături dinamice. Atunci când este rulat un program Windows,
interconectarea acestuia cu sistemul de operare se face prin
procesul de legare dinamică. Un fişier executabil al unui program
Windows conţine referinţe la biblioteci cu legături dinamice.
Atunci când un program pentru Windows este încărcat în
memorie, apelurile de funcţii sunt rezolvate astfel încât să indice
intrările în funcţiile din DLL-urile utilizate, care sunt încărcate în
memorie, în cazul în care nu se află deja acolo.
Principalele biblioteci cu legare dinamică sunt kernel32.dll,
user32.dll şi gdi32.dll. Biblioteca cu legare dinamică kernel32.dll
conţine funcţiile API pentru gestionarea memoriei şi a
proceselor. Biblioteca cu legare dinamică user32.dll controlează
aspectele interfeţei cu utilizatorul ale programului. Biblioteca cu
legare dinamică gdi32.dll este responsabilă cu operaţiile grafice.
În afară de aceste biblioteci principale se pot utiliza în program şi
alte biblioteci cu legare dinamică.
Există două categorii de funcţii API: unele pentru ANSI şi altele pentru
Unicode. Şirurile ANSI sunt vectori de caractere terminate cu NULL. Un caracter
ANSI are dimensiunea de 1 byte. Un caracter Unicode are dimensiunea de 2 octeţi.
Numele funcţiilor API pentru ANSI se termină cu caracterul "A" (de exemplu
28

MessageBoxA), iar numele funcţiilor API pentru Unicode se termină cu W


(MessageBoxW). În mod nativ Windows 95 suportă ANSI iar Windows NT
Unicode. În general în fişierele cu prototipurile funcţiilor există declaraţii
echivalente ale acestora în care se elimină caracterul A, respectiv W (de exemplu
MessageBox).

25.2 Organizarea memoriei în Windows 9x

Programele Win32 rulează în modul protejat, acest mod


existând începând cu microprocesorul 80286. Windows rulează
fiecare program Win32 în spaţii virtuale separate. Aceasta
înseamnă că fiecare program Win32 va avea propriul spaţiu de
adresare de 4 GB. Deoarece programele Win32 au 4 GB de
memorie adresabilă, pointerii pot adresa zone de memorie
cuprinse între 00000000H şi FFFFFFFFH. În Windows 9x ,
fiecare proces obţine propriul spaţiu de memorie de 4 GB, spaţiu
care este în totalitate al său. De fapt el partajează împreună cu
alte aplicaţii care rulează, primii 2 GB de memorie. Este
important să se considere că cei 4GB ai spaţiului procesul sunt
numai şi numai ai acestuia. Acest model de memorie se
numeşte flat.
Secţiunea cea mai joasă a memoriei (regiunea de 64 KB)
este rezervată în Windows 95 pentru atribuirile de pointeri nuli.
Restul memoriei, până la 4 MB este rezervată de sistem pentru
compatibilitatea cu aplicaţiile Win16 şi cu aplicaţiile MS-DOS.
Programele Win32 sunt încărcate la adresa 00400000H
(adresa de bază a aplicaţiilor Win32 în memorie), deci spaţiul de
lucru al programului este până la 7FFFFFFFH. Începând cu
adresa 80000000H, Windows 9x încarcă propriile DLL-uri de
sistem. Începând cu adresa C0000000H până la sfârşitul celor 4
GB se încarcă sistemul de operare Windows 9x precum şi
programele pentru dispozitive virtuale.
Puţine calculatoare au 4GB de memorie. Datorită acestui
lucru, proiectanţii de hardware au introdus ideea de memorie
29

virtuală şi memorie fizică. Memoria virtuală corespunde celor 4


GB de memorie adresabilă, memoria fizică este limitată de
memoria RAM disponibilă în calculator şi de spaţiul pe hard disk.
Memoria este împărţită în pagini, dimensiunea paginii fiind
optimizată în funcţie de suportul hardware, pe sistemele 80x86,
mărimea unei pagini fiind de 4 KB.

FFFFFFFFH
Sistemul de operare Windows 9x
Drivere

C0000000H
4GB
BFFFFFFFH

DLL-uri Win32 partajate


Fişiere mapate în memorie

80000000H

7FFFFFFFH

Programul Win32
2GB
00400000H

003FFFFFH

Compatibilitate cu Win16

00001000H
00000FFFH

Protejata, atribuiri de pointeri NULL


Compatibilitate cu Win16 4MB

00000000H

Figura 25.1 – Organizarea memoriei în Windows 9x

Sistemul de gestiune a memoriei determină paginile


localizate în memoria virtuală şi în RAM şi furnizează un
30

mecanism de conversie a adreselor din memoria virtuală în


adrese fizice. Programul de gestiune a memoriei poate muta sau
comuta paginile de memorie dacă este nevoie de spaţiu
suplimentar de memorie fizică. Windows schimbă memorie în şi
afară din fişierul de paginare de pe hard disk. Dimensiunea
fişierului de paginare împreună cu dimensiunea memoriei RAM
este considerată memoria fizică.
Când este rulat un program, fişierul EXE de pe disc (numit
imaginea programului) este considerat ca făcând parte din
fişierul de paginare de pe hard disk. În acest fel se economiseşte
timpul necesar încărcării fişierului executabil în memoria RAM.
Imaginea fişierului EXE de pe disc este considerată parte a
fişierului de paginare şi secţiunile de cod (nu şi de date) sunt
citite de pe disc atunci când este nevoie de ele.

25.3 Încărcarea programelor Win32

Aplicaţiile Win32 rulează în segmente cu adresare pe 32


de biţi, utilizând modelul de memorie FLAT. Astfel, programul
este în mod automat în modul protejat. Adresele generate şi
utilizate de program se numesc adrese liniare. Registrele de
segment CS, DS, ES şi SS sunt iniţializate astfel că nu mai
contează ce segment este utilizat pentru a adresa o locaţie dată
(o adresă liniară). Adresa de bază la care este încărcat
programul în memorie este 0040000h.
La lansarea în execuţie a programelor, valorile iniţiale ale regiştrilor sunt:

 CS:EIP – începutul programului


 SS:ESP – începutul stivei
 DS=ES=SS – selectorul de date
 FS=TIB (Thread Information Block)
 GS=0, selectorul nul
 CS şi DS sunt mapate pe aceeaşi adresă liniară
 Indicatorul de direcţie, DF, este şters.

Sistemul Windows utilizează intern regiştrii ESI, EDI, EBP şi EBX şi de


aceea presupune că valorile acestor regiştri nu se vor schimba. Se recomandă deci
în cazul utilizării acestor regiştri în cadrul unor funcţii callback să se salveze
31

conţinutul lor înainte de modificare şi să se restaureze înainte de a da controlul


sistemului Windows. O funcţie callback este o funcţie scrisă de programator,
funcţie ce este apelată de Windows. Un exemplu de astfel de funcţie este funcţia
fereastră.

25.4 Structura unui program în limbaj de asamblare

Un program Win32 scris în limbaj de asamblare are


următoarea structură:
.386
.model flat, stdcall
option casemap :none
include \masm32\include\windows.inc

includelib \masm32\lib\user32.lib

.data
;
;datele iniţializate
;
.data?
;
;datele neiniţializate
;
.const
;
;constantele
;
.code
eticheta_de_inceput
;
;codul propriu zis
;
end eticheta_de_inceput

Directiva .386 anunţă asamblorul că se va utiliza setul de


instrucţiuni specifice procesorului 80386. Se pot utiliza şi
directivele .486, .586. Pentru fiecare model al microprocesorul
sunt două forme aproape identice ale acestei directive:
.386/.386p, .486/.486p. Versiunile cu p sunt necesare atunci
când programul utilizează instrucţiuni privilegiate. Instrucţiunile
privilegiate sunt rezervate de microprocesor/sistem de operare
32

în modul protejat. Ele pot fi utilizate numai de cod protejat, cum


ar fi programele pentru echipamente virtuale.
Directiva .model specifică modelul de memorie al
programului. Sub Win32 există numai modelul de memorie flat.
În directiva .model mai este precizata şi convenţia de
transmitere a parametrilor (stdcall). Convenţia de transmitere a
parametrilor specifică ordinea de transmitere a parametrilor (de
la stânga la dreapta sau de la dreapta la stânga) şi de
asemenea cine va echilibra stiva după apelul funcţiei.
Sub Win16 există două tipuri de convenţii de apel: C şi
PASCAL.
Convenţia de apel C transmite parametri de la dreapta la
stânga, cel mai din dreapta parametru fiind pus primul pe stivă.
Apelatorul este responsabil cu echilibrarea stivei după apel. De
exemplu apelul unei funcţii f(int a, int b) utilizând convenţia C de
transmitere a parametrilor în limbaj de asamblare va fi:
push [b]
push [a]
call f
add sp, 8 ; apelatorul echilibrează stiva

Convenţia de apel PASCAL presupune transmiterea


parametrilor de la stânga la dreapta, funcţia apelată fiind
responsabilă cu echilibrarea stivei după apel.
Convenţia de apel C este utilă atunci când nu se ştie exact
numărul de parametri ce vor fi transmişi funcţiei. O astfel de
funcţie este wsprinf. Nu se ştie dinainte câţi parametri vor fi puşi
pe stivă, deci în cadrul funcţiei nu se poate realiza echilibrarea
stivei.
STDCALL este un hibrid între convenţiile de transmitere a
parametrilor C şi PASCAL. Parametri sunt puşi pe stivă de la
dreapta la stânga (ca în convenţia C), însă funcţia apelata este
responsabilă cu echilibrarea stivei (convenţia PASCAL).
Platforma Win32 utilizează exclusiv convenţia STDCALL cu
excepţia funcţiei wsprintf.
33

Directiva option casemap :none anunţă MASM că


etichetele sunt case-sensitive.
Directiva include \masm32\include\windows.inc
inserează fişierul windows.inc în acest punct în programul sursă.
Acest fişier conţine declaraţiile pentru majoritatea constantelor şi
structurilor utilizate de API. După această directivă se urmează
includerea fişierelor ce conţin antetele funcţiilor utilizate din
bibliotecile respective. De exemplu dacă se utilizează funcţia
MessageBox se va scrie include
\masm32\include\user32.inc, având în vedere că această
funcţie există în user32.dll.
Directiva includelib \masm32\lib\user32.lib informează
asamblorul ce biblioteci de import sunt utilizate. Când
asamblorul întâlneşte această directivă, el pune o comandă a
editorului de legături în fişierul obiect astfel încât editorul de
legături să ştie ce biblioteci utilizează programul şi de unde să le
ia.
Secţiunea .data conţine datele iniţializate ale programului.
Secţiunea .data? conţine datele neiniţializate ale programului.
Aceasta are avantajul că fişierul executabil nu îşi măreşte
dimensiunea corespunzător cu mărimea datelor neiniţializate. Ea
doar informează asamblorul cât spaţiu de memorie este necesar
când programul este încărcat în memorie. Secţiunea .const
conţine declaraţiile de constante din program. Aceste nu pot fi
modificate în program.
Secţiunea .code conţine codul programului.
Eticheta_de_inceput este o etichetă ce marchează începutul
codului programului. Prima instrucţiune executabilă se află după
eticheta_de_inceput. Codul trebuie să fie între
eticheta_de_inceput şi end eticheta_de_inceput.
Pe lângă programul sursă, dacă în program se utilizează
resurse ca meniuri, pictograme, căsuţe de dialog, acestea se
definesc într-un fişier special, numit fişier de resurse, fişier ce
are extensia .rc. Acesta este un fişier text în care se descriu
resursele şi se face asocierea cu fişiere existente pe disc. De
exemplu, pentru resursa pictogramă este linia:
34

ID_ICON ICON "WHAT1.ICO"

25.5 Programarea sub Windows

Majoritatea programelor Windows au o fereastră principală asociată. Atunci


când programul este lansat în execuţie, Windows creează o coadă de mesaje pentru
programul respectiv. În această coadă de mesaje sunt păstrate mesajele trimise către
toate ferestrele pe care le creează programul. Programul conţine o mică secvenţă de
cod numită bucla de mesaje care preia mesajele din coada de mesaje şi le distribuie
funcţiilor fereastră corespunzătoare. Anumite mesaje sunt trimise direct funcţiei
fereastră fără a mai fi puse în coada de mesaje.
Paşii necesari creării unei ferestre sunt:
 obţinerea identificatorului instanţei programului
 obţinerea liniei de comandă (opţional)
 înregistrarea clasei fereastră (window class)
 crearea ferestrei
 afişarea ferestrei
 actualizarea zonei client a ferestrei
 intrarea în bucla de mesaje infinită
 dacă sunt mesaje pentru fereastră ele sunt prelucrate de o funcţie
specializată numită şi funcţia fereastră
 terminarea programului
Obţinerea identificatorului instanţei programului se realizează prin apelul
funcţiei GetModuleHandle. Funcţia returnează identificatorul instanţei curente a
programului. Prototipul funcţiei este:
HMODULE GetModuleHandle(LPCTSTR lpModuleName);

Prin transmiterea ca parametru a valorii NULL se obţine identificatorul


instanţei curente. După apelul funcţiilor API, rezultatul returnat se găseşte în
registrul EAX.
.data?
;...
hinst dd ?
;...
.code
;...
push 0
call GetModuleHandle
mov hinst,eax
;...
35

Obţinerea liniei de comandă se face cu funcţia GetCommandLine. Obţinerea


liniei de comanda nu este necesară decât dacă programul prelucrează parametrii din
linia de comandă.
Înregistrarea clasei fereastră se realizează având în vedere faptul că o
fereastră este întotdeauna creată pe baza unei clase de fereastră. Aceasta identifică
procedura de fereastră care prelucrează toate mesajele transmise către fereastră. Pe
baza aceleiaşi clase se pot crea mai multe ferestre.
Înainte de crearea ferestrei programului trebuie înregistrată clasa fereastră
prin apelul funcţiei RegisterClassEx ce are un singur parametru: un pointer la o
structură de tipul WNDCLASSEX.
Structura WNDCLASSEX are următorii membri:
WNDCLASSEX struc
cbSize dd ? ; dimensiunea structurii WNDCLASSEX
style dd ? ; stilul clasei fereastră
lpfnWndProc dd ? ; pointerul la funcţia fereastră
cbClsExtra dd ? ; informaţii suplimentare
;pentru clasă
cbWndExtra dd ? ; informaţii suplimentare
;pentru fereastră
hInstance dd ? ; identificatorul instanţei
;curente
hIcon dd ? ; identificatorul pictogramei
;asociata clasei
hCursor dd ? ; cursorul asociat clasei
hbrBackground dd ? ; culoarea de fundal a
;ferestrelor din clasă
lpszMenuName dd ? ; pointer la şirul cu denumirea
;meniului
lpszClassName dd ? ; pointer la şirul cu numele
;clasei
hIconSm dd ? ; identificatorul pictogramei
;aplicatiei
WNDCLASSEX ends

Al treilea şi penultimul câmp (lpfnWndProc şi lpszClassName) sunt cele


mai importante, ele conţinând adresa procedurii fereastră folosită de toate ferestrele
create pe baza acestei clase, respectiv numele clasei fereastră. Celelalte câmpuri
descriu caracteristicile tuturor ferestrelor create pe baza acestei clase.
Înainte de apelul funcţiei RegisterClassEx trebuie iniţializate câmpurile
variabilei de tip WNDCLASSEX. Iniţializarea câmpurilor se realizează în
următoarea secvenţă de cod:
;...
.data?
;...
wcex WNDCLASSEX <?>;var de tip structura clasa fereastra
NumeFer db 'Programare sub Windows',0 ;Numele ferestrei
36
hinst dd ? ;identificatorul instantei curente
;...
.code
;...
;completarea cimpurilor structurii fereastra
mov wcex.cbSize, size WNDCLASSEX
mov wcex.style, CS_VREDRAW or CS_HREDRAW or CS_DBLCLKS
mov wcex.lpfnWndProc, offset WndProc
mov wcex.cbClsExtra,0
mov wcex.cbWndExtra,0
push hinst
pop wcex.hInstance

;se incarca pictogtama, definita in fisierul .rc


push ID_ICON ;icon id
push hinst
call LoadIcon
mov wcex.hIcon,eax

;se incarca cursorul sageata standard


push IDC_ARROW
push NULL
call LoadCursor

mov wcex.hCursor,eax
mov wcex.hbrBackground,COLOR_WINDOW+1
mov wcex.lpszMenuName,0
mov wcex.lpszClassName,offset NumeFer
mov wcex.hIconSm,0

Se observă că pentru iniţializarea câmpurilor hIcon şi hCursor ale structurii


WNDCLASSEX s-au apelat funcţiile LoadIcon şi LoadCursor. Funcţia
LoadIcon are prototipul:
HICON LoadIcon( HINSTANCE hInstance, // identificatorul
instantei aplicatiei
LPCTSTR lpIconName // numele identificatorul resursei
pictograma
);

Funcţia returnează un identificator al pictogramei asociat resursei


pictogramă definită în fişierul de resurse. În program se transmite ID_ICON,
definit în fişierul de resurse.
Funcţia LoadCursor:
HCURSOR LoadCursor( HINSTANCE hInstance, // identificatorul
instantei aplicatiei
LPCTSTR lpCursorName // numele/identificatorul resursei
cursor
);
37

returnează identificatorul de cursor asociat cursorului definit în fişierul de resurse,


sau unuia din cursoarele standard. În aplicaţie s-a utilizat cursorul standard de tip
săgeată, identificat prin IDC_ARROW. Atunci când se utilizează cursoare
standard, parametrul hInstance al funcţiei LoadCursor va fi 0.
Apelul funcţiei RegisterClassEx prin care se înregistrează clasa fereastră:

.code
;...
;Inregistrarea clasei fereastra
push offset wcex
call RegisterClassEx
;...

În cazul în care nu s-a reuşit înregistrarea clasei de fereastră, funcţia


RegisterClassEx returnează valoarea 0.
Sunt definite câteva clase standard de ferestre înregistrate: "BUTTON",
"EDIT", "LISTBOX" şi altele, clase fereastră ce nu mai trebuie înregistrate, ele
având predefinite funcţiile fereastră şi tratarea anumitor mesaje. Pe baza acestor
clase de ferestre standard se pot crea ferestre de acel tip, modificându-se anumite
caracteristici, modul de funcţionare rămânând acelaşi.
Clasa fereastră defineşte caracteristicile generale ale unei ferestre,
permiţând folosirea acesteia pentru crearea mai multor ferestre. Când se creează o
fereastră cu funcţia CreateWindowEx se pot specifica mai multe detalii despre
fereastra respectivă (cum ar fi titlul ferestrei, stilul acesteia, poziţia, meniul asociat
şi dimensiunea iniţială).

Prototipul funcţiei CreateWindowEx este:


HWND CreateWindowEx(
DWORD dwExStyle, // stilul extins al ferestrei
LPCTSTR lpClassName, // pointer către numele clasei
înregistrate
LPCTSTR lpWindowName,// pointer către numele ferestrei
DWORD dwStyle, // stilul ferestrei
int x, // poziţia orizontală a ferestrei
int y, // poziţia verticală a ferestrei
int nWidth, // lăţimea ferestrei
int nHeight, // înălţimea ferestrei
HWND hWndParent, // indicator către fereastra
părinte
HMENU hMenu, // indicator către meniu sau
fereastra copil
HINSTANCE hInstance, // indicatorul instanţei curente a
programului
LPVOID lpParam // pointer către datele de creare
a ferestrei
38
);

Funcţia returnează identificatorul ferestrei create.


Apelul funcţiei CreateWindowEx este:
.data?
;...
hwnd dd ? ;identificatorul de fereastra
hinst dd ? ;identificatorul instantei curente
hmeniu dd ? ;identificatorul meniului
;...
.const
;...
NumeFer db 'Programare sub Windows',0 ;Numele ferestrei
NumeApp db 'Programare in Win32 ASM',0;Numele aplicatiei
;...
.code
;...
;Crearea ferestrei

push ID_MENIU
push hinst
call LoadMenu
mov hmeniu,eax

push 0 ; lpParam
push hinst ; hInstance
push hmeniu ; identificator meniu
push 0 ; identificator parinte
push CW_USEDEFAULT ; inaltime
push CW_USEDEFAULT ; latime
push CW_USEDEFAULT ; y
push CW_USEDEFAULT ; x
push WS_OVERLAPPEDWINDOW ; stilul
push offset NumeApp ; titlul ferestrei
push offset NumeFer ; numele clasei
push WS_EX_OVERLAPPEDWINDOW ; stilul extins
call CreateWindowEx
mov hwnd,eax
;...

Pentru a asocia resursa meniu definită în fişierul de resurse se apelează


funcţia LoadMenu ce are ca parametri identificatorul instanţei curente a aplicaţiei
şi numele meniului definit în fişierul de resurse. Funcţia returnează identificatorul
de meniu asociat.
După apelul funcţiei CreateWindowEx, fereastra a fost creată însă nu a
fost şi afişată pe ecran. Pentru afişarea ferestrei sunt necesare încă două apeluri de
funcţii: ShowWindow respectiv UpdateWindow.
Funcţia ShowWindow are prototipul:
39
BOOL ShowWindow(HWND hWnd, // identificatorul ferestrei
int nCmdShow // modul de afişare a ferestrei);

Apelul funcţiei în cadrul programului:


push SW_SHOWNORMAL
push hwnd
call ShowWindow

Funcţia UpdateWindow redesenează zona client a ferestrei. Prototipul acesteia


este:
BOOL UpdateWindow(HWND hWnd // identificatorul ferestrei
);

În program, apelul se realizează astfel:


push hwnd
call UpdateWindow

După apelul funcţiei UpdateWindow, fereastra devine vizibilă pe ecran.


Programul trebuie să poată prelucra intrările de la tastatură şi de mouse.
Windows creează o coadă de mesaje asociată fiecărui program. Când apare un
eveniment, acesta este transformat de Windows într-un mesaj care este pus în
coada de mesaje a programului. Mesajele sunt preluate din coada de mesaje prin
intermediul buclei de mesaje. În pseudocod bucla de mesaje se reprezintă astfel:

Aşteaptă_mesaj:
Dacă este apare un mesaj, preia mesajul
Dacă mesajul este QUIT Atunci Ieşire
Apelează TranslateMessage
Apelează DispatchMessage
Trimite mesajul către procedura fereastră
Înapoi la Aşteaptă_mesaj
Ieşire

Funcţia GetMessage preia un mesaj din coada de mesaje. Prototipul


funcţiei GetMessage:

BOOL GetMessage(
LPMSG lpMsg, // adresa structurii cu mesajul
HWND hWnd, // identificatorul ferestrei
UINT wMsgFilterMin, // primul mesaj
UINT wMsgFilterMax // al doilea mesaj
);
40

Primul parametru este un pointer la o structură de tip MSG. Windows


completează câmpurile structurii de mesaje cu următorul mesaj din coada de
aşteptare. Structura este MSG este:
MSG STRUC
hwnd DWORD ? ; identificatorul ferestrei căreia îi
este destinat mesajul
message DWORD ? ; identificatorul mesajului
wParam DWORD ? ; parametru dependent de mesaj
lParam DWORD ? ; parametru dependent de mesaj
time DWORD ? ; momentul inserării mesajului în coada
de mesaje
pt POINT <>; poziţia mouse-ului în momentul
inserării mesajului
MSG ENDS

Dacă în câmpul message al structurii este transmisă o valoare diferită de


WM_QUIT, funcţia GetMessage returnează o valoare diferită de zero. Mesajul
WM_QUIT determină ieşirea din bucla de mesaje.
În cadrul buclei de mesaje se apelează două funcţii. TranslateMessage
transmite structura MSG sistemului de operare pentru convertirea unor mesaje de
la tastatură. Prototipul acesteia este:
BOOL TranslateMessage( CONST MSG *lpMsg // pointer la o
structură MSG
);

Funcţia DispatchMessage, al cărei prototip este:


LONG DispatchMessage(
CONST MSG *lpmsg // pointer către structura ce conţine
mesajul
);

retransmite structura MSG sistemului de operare.


Windows trimite apoi mesajul către funcţia fereastră corespunzătoare în
vederea prelucrării acestuia.
Bucla de mesaje este implementată astfel:
;Bucla de mesaje

start_bucla:
push 0 ; wMsgFilterMax
push 0 ; wMsgFilterMin
push NULL ; 0 - toate ferestrele
push offset msg ; lpMsg
call GetMessage ; returneaza FALSE pentru WM_QUIT
or eax,eax
41
jz iesire

push offset msg


call TranslateMessage
push offset msg
call DispatchMessage
jmp start_bucla

Terminarea programului se face prin apelul funcţiei ExitProcess. Prototipul


acesteia este:
VOID ExitProcess(UINT uExitCode // codul de ieşire al
procesului
);

Secvenţa de cod în care se face apelul funcţiei este:

push 0 ;codul de iesire returnat de aplicatie


call ExitProcess

Funcţia fereastră este responsabilă de afişarea în zona client a ferestrei,


precum şi de tratarea mesajelor provenite de la tastatură şi mouse. Funcţia
fereastră poate avea orice nume, iar un program pentru Windows poate avea mai
multe funcţii fereastră, asociate ferestrelor definte în cadrul acestuia. Funcţia
fereastră este întotdeauna asociată unei clase fereastră.
Funcţia fereastră are următorul prototip:

LRESULT WndProc(HWND hwnd, //identificatorul ferestrei


UINT msg, //identificatorul mesajului
WPARAM wparam, //parametru asociat mesajului
LPARAM lparam //parametru asociat mesajului
);

Parametrii funcţiei corespund primelor patru câmpuri ai structurii MSG.


Fiecare mesaj recepţionat de fereastră este identificat printr-un număr (parametrul
msg al funcţiei fereastră). În fişierele antet din Windows acestora le corespund
identificatori prefixaţi de WM_ (de la window message)
Pentru tratarea diverselor mesaje se utilizează o instrucţiune switch pentru
determinarea mesajelor primite de fereastră şi a modului de prelucrare a acestuia.
Atunci când prelucrează un mesaj funcţia fereastră trebuie să returneze valoarea
zero. Toate mesajele ce nu sunt prelucrate de funcţia fereastră sunt transmise
funcţie DefWindowProc. Prototipul funcţiei DefWindowProc este:
LRESULT DefWindowProc(
HWND hWnd, // identificatorul ferestrei
UINT Msg, // mesajul
WPARAM wParam, // primul parametru al mesajului
42
LPARAM lParam // al doilea parametru al mesajului
);

Un mesaj ce trebuie tratat este WM_DESTROY care este trimis atunci când
utilizatorul doreşte să închidă fereastra. Mesajul este tratat standard, prin apelul
funcţiei PostQuitMessage, ce are prototipul:

VOID PostQuitMessage(
int nExitCode // codul de ieşire
);

Implementarea funcţiei fereastră, care tratează mesajul WM_DESTROY


este:
WndProc proc
;parametrii de pe stiva sunt:
;esp+4 --> hwnd
;esp+8 --> msg
;esp+12 --> wParam
;esp+16 --> lParam
cmp dword ptr [esp+8],WM_DESTROY
je msg_wm_destroy
jmp DefWindowProc
msg_wm_destroy:
push 0
call PostQuitMessage
xor eax,eax
ret 16
WndProc endp

Celelalte mesaje sunt tratate de funcţia standard.


În programul scris în limbaj de asamblare nu are importanţă dacă numele
funcţiei fereastră este WndProc, acest lucru fiind la latitudinea programatorului.

25.6 Asamblarea şi editarea de legături

Pentru realizarea programului s-a utilizat MASM32. Acesta se găseşte la


adresa http://www.pbq.com.au/home/hutch/masm.htm.
Asamblarea programului sursă se realizează astfel:

ml /c /coff /Cp program.asm

Dacă se folosesc fişiere de resurse, acestea trebuiesc compilate cu un


compilator de resurse (de exemplu rc.exe), obţinându-se un fişier de resurse obiect,
fişier ce va fi link-editat împreună cu fişierul obiect al programului. Editarea de
legături se realizează utilizând următoare linie de comandă:
43

link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib program.obj

25.7 Exemplu de program Win32

În continuare se prezintă un program Windows, program care afişează o


fereastră, în funcţia fereastră tratându-se mesajele WM_COMMAND (alegerea
unei opţiuni din meniu), WM_LBUTTONDOWN (executarea unui clic de mouse
în zona client a ferestrei) şi WM_DESTROY (la închiderea ferestrei).
Fereastra are asociat un meniu având submeniul "Fisiere" cu o un singur
articol: "Iesire" şi submeniul "Help" cu articolul "Despre...". Când se selectează
"Iesire" se va închide fereastra, iar când se selectează "Despre..." se afişează o
căsuţă de dialog.

.386
.model flat, stdcall
option casemap :none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

include res.equ

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data

.const
NumeFer db 'Programare sub Windows',0 ;Numele
ferestrei
NumeApp db 'Programare in Win32 ASM',0 ;Numele
aplicatiei
MesajDespre db 'Paul Pocatilu, 1999',0
Mesaj1 db 'Mouse click',0

.data?
wcex WNDCLASSEX <?> ;var de tip structura clasa fereastra
msg MSG <?> ;Var de tip MSG
hwnd dd ? ;identificatorul de fereastra
hinst dd ? ;identificatorul instantei curente
hmeniu dd ? ;identificatorul de meniu

.code
start:

;obtinerea hInstance
44
push 0
call GetModuleHandle
mov hinst,eax

;completarea campurilor structurii fereastra


mov wcex.cbSize, size WNDCLASSEX
mov wcex.style, CS_VREDRAW or CS_HREDRAW or CS_DBLCLKS
mov wcex.lpfnWndProc, offset WndProc
mov wcex.cbClsExtra,0
mov wcex.cbWndExtra,0
push hinst
pop wcex.hInstance

;se incarca iconul, definit in fisierul .rc


push ID_ICON ;icon id
push hinst
call LoadIcon
mov wcex.hIcon,eax

;se incarca cursorul sageata standard


push IDC_ARROW
push NULL
call LoadCursor

mov wcex.hCursor,eax
mov wcex.hbrBackground,COLOR_WINDOW+1
mov wcex.lpszMenuName,0
mov wcex.lpszClassName,offset NumeFer
mov wcex.hIconSm,0

;Inregistrarea clasei fereastra


push offset wcex
call RegisterClassEx

;s-a inregistrat fereastra?


or eax,eax
jz iesire

;Crearea ferestrei

push ID_MENIU
push hinst
call LoadMenu
mov hmeniu,eax

push 0 ; lpParam
push hinst ; hInstance
push hmeniu ; identificator meniu
push 0 ; identificator parinte
push CW_USEDEFAULT ; inaltime
push CW_USEDEFAULT ; latime
push CW_USEDEFAULT ; y
45
push CW_USEDEFAULT ; x
push WS_OVERLAPPEDWINDOW ; stilul
push offset NumeApp ; titlul ferestrei
push offset NumeFer ; numele clasei
push WS_EX_OVERLAPPEDWINDOW ; stilul extins
call CreateWindowEx
mov hwnd,eax

;se afiseaza fereastra


push SW_SHOWNORMAL
push hwnd
call ShowWindow

;se actualizeaza zona client


push hwnd
call UpdateWindow

;Bucla de mesaje

start_bucla:
push 0 ; wMsgFilterMax
push 0 ; wMsgFilterMin
push NULL ; 0 - toate ferestrele
push offset msg ; lpMsg
call GetMessage ; returneaza FALSE pentru WM_QUIT
or eax,eax
jz iesire

push offset msg


call TranslateMessage
push offset msg
call DispatchMessage
jmp start_bucla

iesire:
;Terminarea programului
push 0 ; codul de iesire returnat de aplicatie
call ExitProcess

;Functia Fereastra

WndProc proc
cmp dword ptr [esp+8],WM_COMMAND
je msg_wm_command
cmp dword ptr [esp+8],WM_DESTROY
je msg_wm_destroy
cmp dword ptr [esp+8],WM_LBUTTONDOWN
je msg_wm_lbuttondown
jmp DefWindowProc
msg_wm_command:
;se testeaza alegerea optiunilor din meniu
46
cmp dword ptr [esp+12],ID_FISIERE_IESIRE
je msg_wm_destroy
cmp dword ptr [esp+12],ID_HELP_DESPRE
je help_despre
xor eax,eax
ret 16
msg_wm_lbuttondown:
;s-a facut click in zona client a ferestrei
;se afiseaza o casuta de dialog
;avind ca titlu NumeApp si mesaj Mesaj1

push MB_OK
push offset NumeApp
push offset Mesaj1
push NULL
call MessageBox
xor eax,eax
ret 16
help_despre:
;s-a ales optiunea Despre... din meniul Help
;se afiseaza o casuta de dialog
;avind ca titlu NumeApp si mesaj MesajDespre

push MB_OK
push offset NumeApp
push offset MesajDespre
push NULL
call MessageBox
xor eax,eax
ret 16
msg_wm_destroy:
;s-a inchis fereastra

push 0
call PostQuitMessage
xor eax,eax
ret 16
WndProc endp

end start

Fişierul de resurse asociat are următorul conţinut:

#define ID_ICON 101


#define ID_MENIU 102

ID_ICON ICON "MAIN.ICO"

ID_MENIU MENUEX
BEGIN
47
POPUP "&Fisiere", , , 0
BEGIN
MENUITEM "&Iesire", ID_FISIERE_IESIRE
END
POPUP "&Help", , , 0
BEGIN
MENUITEM "&Despre...", ID_HELP_DESPRE
END
END

La lansarea în execuţie, programul va afişa o fereastră, dimensiunile şi


poziţia acesteia fiind generate de sistemul Windows (prin utilizarea
CW_USEDEFAULT). Prin apăsarea butonului stâng al mouse-ului în zona client a
ferestrei, prin tratarea evenimentului WM_LBUTTONDOWN se va activa o căsuţă de
dialog. Închiderea ferestrei se realizează fie în mod clasic, fie prin alegerea opţiunii
Iesire din meniul Fisiere.

BIBLIOGRAFIE

[ABEL95] IBM PC Assembly Language and Programming 3rd -


Edition Prentice Hall International, Inc, New Jersey, 1995
[ATHA92] Irina Athanasiu, Alexandru Panoiu Microprocesoarele
8086, 286, 386 Editura Teora, Bucureşti, 1992
[BARK90] Nabajyoti Barkakati, The Waite Group's Microsoft Macro
Assembler Bible, SAMS, Carmel Indiana, 1990
[BETH87] RWMc Beth, J. R. Ferguson, IBM Assembler John Wiley &
Sons, New York, 1987
[BREY90] Barry B. Brey, 8086/8088, 80286, 80386 and 80486
Assembly Language Programming, Macmillan Publishing
Company, New York, 1990
[BRUM88] Penn Brumm, Don Brumm, 80386 – Assembly Language A
Complete Tutorial and Subroutine Library TAB Books Inc,
Blue Ridge Summit, 1988
[BUY96] Barry Buy, Programming the 80286, 80386, 80486 and
Pentium - Based Personal, Prentice Hall Englewood Cliffs,
New Jersey, 1996
[CAPR91] Vlad Căprariu, Sisteme de operare DOS - Funcţii Sistem,
Microinformatica, Ediţia a III-a, Cluj Napoca, 1991
[CATO74] I. Catona, I. Teodorescu, C. Popescu, Sistemul Felix C -
256, Limbajul ASSIRIS, Editura Academiei, Bucureşti,
1974
[Coff87] James W. Coffron, Programming the 8086/8088, Ed.
Kleidarifmos, Athens, 1987
48

[COHE94] Le microprocesseur Pentium Architecture et


programmation, Armand Colin, Paris, 1994
[CUOR93] Sen - Cuo Ro Shean - Chuen Her, i386/i486 Advanced
Programming, Van Nosteand Reinhold, New York, 1993
[DAVI91] A. Davidoviciu, Gh. Dodescu, coordonatori MIX şi
MACRO, vol. 2, Programarea în limbajul MACRO, Editura
Tehnică, Bucureşti, 1991
[DETM90] Richard C. Detmer, Fundametals of Assembly Language
Programming, PC Heath Comp., Lexington Masschusetts,
1990
[DIAC75] G. Diaconescu, I. Lungu, Assembler, Lito ASE, Bucureşti,
1975
[DIMO92] Dimopoulos K. Z., Paraskevopoulos A.S.80x86
Architictonici schediasi & Programatismos Papasotiriu Ed.
Athens, 1992
[DORF90] Len Dorfman, Object Oriented Assembly Language
Windcrest, 1990 Mc Graw Hill Inc. New York
[DORF90a] Len Dorfman, Structured Assembly Language, Windcrest
Books Blue Ridgs Summit, 1990
[DOVI91] A. DOVIDOVICIU, Gh. DODESCU Mix si Macro (vol. 2)
Programarea in limbajul MACRO Editura Tehnica,
Bucureşti, 1991
[FITZ86] Robert M. Fitz, Larry Crokett, Universal Assembly
Language, TAB Books Inc. Blue Ridge Summit PA 17214,
1986
[GHEO92] MARIAN GHEORGHE, BADICA COSTIN, PATRASCOIU
OCTAVIAN Limbaje de asamblare MACRO-11, Intel
8086/88, Lito Universitate, Craiova, 1992
[GILL94] Frame van Gilluwe, The Undocumented PC, Addison
Wesley Publishing Company Reading, 1994
[GURT89] A.L. Gurtovtev, S.V. Gudimenko Programi dlia
microprotessorov, Editura Visaisaia Scola, Minsk, 1989
[HABI88] Stanley Habib Microprogramming and firmware
engineering methods, van Nostrand Reinhold, New York,
1988
[HALL80] Douglas V. Hall, Microprocessors and interfacing
Programming and hardware, Mc. Craw Hill International
New York, 1980
[HAWK81] Gerry Kan, Danny Hawkins, Lance Leventhal 6800
Assembly Language Programming, Osborne, California,
1981
[HOLZ87] Steven Holzner, Advanced Assembly Language on the IBM
49

PC, Brady - New York, 1987


[HOLZ90] Steven Holzer, Assembly Language for Pascal, Ed. Brady,
New York, 1990
[HOLZ96] Holzner, Steve – Advanced Visual C++ 4, M&T Books,
New York 1996
[HORS91] Gordon Horsington Programming in ANSI standard C,
Sigma Press, Wilmslow, England, 1991
[iAPX83] ***** iAPX 286. Programmer's Reference Manual, Intel
Product Guide, Intel Literature Dept., Santa Clara 1983
[IRVI93] KIP R. IRVINE, Assembly Language for the IBM – PC
Mcmillan Publishing Company, New York, 1993
[IVAN96] Ion Ivan , Cristian Codreanu, Optimizarea programelor
asembler, ASE – Departamentul de Informatică
Economică, Bucureşti, 1996
[IVAN97] Ion Ivan Designul limbajelor de asamblare, Revista
Informatica Economică, nr. 1/1997
[IVAN98] Ion Ivan, Cristian Codreanu Optimizarea programelor
assembler, Revista Informatica Economică, nr. 6/1998
[IVAN98a] Ion Ivan, Marian Dardala, Gabriel Sutac, Valentin
Dragomir, Referirea datelor in structuri complexe prin
proceduri assembler, Informatica Economica, vol. III, nr. 7,
Trim III/1998
[IVAN99] Ion Ivan, Adrian Licuriceanu, Sebastian Tcaciuc, Gheorghe
Lupu, Aplicatii orientate obiect in limbaj de asamblare,
"Studii si cercetari de calcul economic si cibernetica
economica", nr 3/1999, Bucuresti, 1999
[JONE91] D.S. Jones 80x86 Assembly Programming, Oxford
University Press, New York, 1991
[KACM95] Gary Kacmarcik, Optimizing Power PC code -
Programming the Power PC chip in Assembly Language,
Addison Wesley Publishing Company Massachusetts,
1995
[LEVE87] Lance Leventhal 80387 Programming guide, Bantan
Books, Toronto, 1987
[LIUY86] Liu Yu-Cheng Microcomputer Systems: the 8086/8088,
Family Prentice Hall Englewood Cliffs, New Jersey, 1986
[LUCA85] Dan Luca Serbanuta, Valentin Cristea, Claudiu Popescu
Limbajul MACRO-11. Îndrumar de laborator, IPB,
Bucureşti, 1985
[LUPU82] Cristian Lupu, Vlad Tepelea, Emil Purice Microprocesoare-
aplicaţii, Editura Militară, Bucureşti, 1982
[LUPU82] Cristian Lupu, Vlad Ţepelea, Emil Purice Microprocesoare -
50

Aplicaţii, Editura Militară, Bucureşti, 1982


[MARI92] Marian Gheorghe, Badica Costin, Pătrăşcoiu Octavian
Limbaje de asamblare Macro 11 – Intel 8086/88, Îndrumar
de Laborator, Lito Universitatea Craiova, 1992
[MASM78] ***** MCS-86tm Macro Assembly language reference
manual (manual order number 9806640-02), Intel
Corporation, Santa Clara, 1978
[MASM86] ***** MStm - DOS 3.1 Macro ASSEMBLER Manual User's
guide Masm Reference Manual, NEC Corporation 1986,
Printed in Japan
[MAST95] * * * , ASSEMBLY Language Master Class, Wrox Press
Ltd., Birmingham, 1995
[MATS76] Y. Matsumoto AA: Assembly Automation 1302XC 13O1XC
[MCBE87] Robert W. Mc.Beth, J. Robert Ferguson IBM ASSEMBLER,
John Wiley & Sons, New York, 1987
[MORG84] Morgan Cristopher L., Assembly Language Routines for
the IBM PC & T, New York, Waite Group's, 1984
[MORRIS] A.J. T. Davie, R. Morrison Recursive descent compiling,
University of St. Andrews, Scotland
[MUNT76] E. Munteanu, V. Costea, N. Mitrov Programare in limbajul
de asamblare ASSIRIS, Editura Tehnică, Bucureşti, 1976
[MURR88] Assembly Language Programming under OS/2, Osborne
Mc Gran Hill Berkley, 1988
[MUSC96] Gheorghe Muscă Programare în limbaj de asamblare,
Editura Teora, Bucureşti, 1996
[NORT89] Peter Norton, John Socha, Peter Norton's Assembly
Language Book for the IBM PC, Brady – New York, 1989
[PETZ98] Petzold, Charles, Yao, Paul Programare în Windows 95,
Editura Teora, Bucureşti, 1998
[PRET75] T. W. Prett Programming languages design and
implementation, Prentice Hall Inc. Englewood Cliffs NJ,
1975
[RICH94] Richard P. Paul, SPARC Architecture Assembly Language
Programming & C., Prentice Hall, Englewood Cliffs, New
Jersey, 1994
[RODE96] Liviu Rodean, Optimizarea programelor elaborate în
limbaje de asamblare, Lucrare de licenţă, ASE - Bucureşti,
1996
[ROŞC77] V. Roşca, C. Apostol, I. Ivan, I. Roşca, Limbaje de
programare, Limbajul ASSIRIS, vol 1, 2, Lito ASE,
Bucureşti, 1977
[RUNN88] Wiliam C. Runnion, Structured Programming in Assembly
51

Language for IBM PC, PWS - Kent Publishing Company,


Boston, 1988
[SACH93] Holger SCHAKEL, Programmer en Assembleur sur PC,
Edition Micro Application, Paris, 1993
[SALE95] Cristian Salescu, Implementarea mecanismelor de
recursivitate în limbaje de asamblare – Lucrare de licenţă -
ASE, Bucureşti, 1995
[SANC90] Julio Sanchez, Assembly Language tools and techniques
for the IBM Micro computers, Prentice Hall Englewood
Cliffs, New Jersey, 1990
[SANC94] Julio Sanchez, Maria P. Canton, PC Programming Hand
book Mc Gron Hill Inc, New York, 1994
[SANC95] Julio Sanchez, Maria P. Canton, Numerical Programming
the 387, 486 and PentiumTM, Mc Graw Hill, Inc., New
York, 1995
[SCAN83] Leo J Scanlon, IBM PC & XT Assembly Language A guide
for programmers, Brady Communications Company Inc.,
New York, 1983
[SCAN87] Leo J Scanlon, Assembly Language Subroutines for MS-
DOS Computers, Blue Ridge Summit PA TAB Boccks,
1987
[SCAN88] Leo J. Scanlon, 8086/8088/80286, Assembly Language A
Brady Book, New York, 1988
[SCHA93] Holger Schakel Programmer in assembleur sur PC, Edition
MicroAplication, Paris, 1993
[SCHM95] Michael L. Schmit, Pentiumtm Processor Optimization tools
AP Professional, New York, 1995
[SENC93] Sen-Cuo Ro, Shean Chuen Hen i386/i486 Advanced
Programming, Van Nostrand Reinhold, New York, 1993
[SERA87] Luca Dan Şerbănaţi, Limbaje de asamblare şi asambloare
în Limbaje de programare şi compilator, Editura Academiei,
Bucureşti, 1987
[SERB85] Luca Dan Şerbănaţi, Valentin Cristea, Claudiu Popescu,
Limbajul MACRO-11, Îndrumar de Laborator, Lito IPB,
Bucureşti, 1985
[SOCE75] A. Soceneantu, G. Gavrilescu, T. Ilin Limbaje de asamblare
pentru calculatoarele electronice numerice. Asambloare,
Editura Facla, Timişoara, 1975
[SOMN92] Dan Somnea, Vlăduţ Teodor, Programarea în Assembler,
Editura Tehnică, Bucureşti, 1992
[STRU92] Crişan Strugaru, Mircea Popa, Microprocesoare pe 16 biţi,
Editura TM, Timişoara, 1992
52

[SWAN95] Tom Swan, Mastering Turbo Assembler, SAMS Publishing


Indianapolis, 1995
[TASM88] ***** Turbo ASSEMBLER version 2.0 User's guide, Borland
International Inc., Green-Hils road, Scotts-Valley, 1988
[TASM99] ***** Turbo ASSEMBLER 3.0 User's guide, Borland
International Inc., Scotts-Valley, 1988
[THOR90] Michael Thorne, Computer Organisation and Assembly
Language Programming, The Benjamin Cummig
Publishing Comp. , Bonn, 1990
[TISC94] Michael Tischer, La Bible PC 5e édition, Edition Micro
Application, Paris, 1994
[TRIE90] Triebel A Walter, Singh Avtar, Microprocesorul 8086
Arhitectură, software şi tehnici de interfaţare, Editura
Mitron, Timişoara, 1990
[TURL88] James L. Turley, Advanced 80386 Programming
techniques , Osborne Mc Graw Hill Ber Kley, 1988
[WYAT87] Allen L. Wyatt, Using Assembly Language, QUE
Corporation, Carmel, Indiana, 1987
[WYAT92] Allen L. Wyatt Jr., Advanced Assembly Language, QUE
Corporation Carmel, 1992
[YUCH86] YU Cheng Liu, Glen A. Gibson Microcomputer Systems
The 8086/8088 Family Arhitecture Programming and
Designe Prentice Hall International, Inc. New Jersey, 1986
[******] http://win32asm.cjb.net, " Iczelion's Win32 Assembly
HomePage"
[******] http://www.pbq.com.au/home/hutch/masm.htm, "MASM32"

Anda mungkin juga menyukai