Sunteți pe pagina 1din 50

Capitolul 5 Tehnici de programare n limbaj de asamblare Acest capitol este dedicat tehnicilor de programare n limbaj de asamblare, adic modalitilor

de proiectare i implementare a modulelor de program. Dei codul main rezultat este mai scurt, programele surs scrise n ASM tind s aib dimensiuni mari. De aceea, este esenial o abordare sistematic i ordonat a dezvoltrii programelor. Utilizarea disciplinat a procedurilor, a mecanismelor standard de transfer de parametri i a macroinstruciunilor contribuie esenial la obinerea de programe clare, eficiente i uor de ntreinut. n esen, specificarea modulelor de program ASM nu difer de cea specific limbajelor de nivel nalt, n sensul c se pornete de la o descriere abstract a algoritmului care trebuie implementat. O problem specific este asignarea variabilelor. Dac ntr-un limbaj de nivel nalt acest lucru nu creeaz probleme (introducem cte variabile dorim, fr a ne pune problema spaiului alocat), n ASM trebuie s asignm explicit variabile. Prima modalitate este s asignm ct mai multe variabile n registrele procesorului. Cum numrul acestora este limitat, vom fi nevoii s asignm variabile i n segmente de date (statice) sau n stiv. Pentru a nu crete numrul variabilelor peste o limit rezonabil, este esenial ca modulele de program s implementeze subprobleme de dimensiuni adecvate (nu foarte mari), ceea ce implic o descompunere a problemei iniiale n subprobleme bine specificate. 5.1 Decizia simpl i decizia compus. Evaluarea condiiilor logice Majoritatea operaiilor de baz din programarea structurat (decizia, selecia, ciclurile cu test la partea inferioar i superioar) implic n mod inerent evaluarea unor condiii logice. n ASM, aceste condiii sunt n general de tip comparaie ntre valori numerice. Decizia simpl, codificat n pseudo-cod prin: if (conditie) Ramura_if se implementeaz n ASM prin ablonul: Evalueaza conditie Salt conditionat (pe conditie falsa) la eticheta_1 ; Ramura_if eticheta_1: Decizia compus, codificat n pseudo-cod prin: if (conditie) Ramura_if else Ramura_else se implementeaz dup ablonul: Evalueaza conditie Salt conditionat (pe conditie falsa) la et_1 ; Ramura_if jmp et_2 et_1: ; Ramura_else et_2: Evaluarea condiiilor logice simple se realizeaz prin instruciuni de comparaie, aritmetice etc., care poziioneaz bistabilii de condiie. De exemplu, secvena pseudo-cod: if (ax < bx) ; Ramura_if else ; Ramura_else se implementeaz prin: cmp ax, bx

jge et1: ; Ramura_if jmp et2 et1: ; Ramura_else et2: Evaluarea condiiilor complexe se abordeaz n manier ordonat. Primul caz de baz este cel n care subcondiiile sunt conectate prin operatorul logic AND. Astfel, s considerm o condiie logic de forma: C = C1 AND C2 AND C3 AND ... AND Cn i decizia compus: if (C) ; Ramura_if else ; Ramura_else Implementarea este urmtoarea: Evalueaza C1 Salt conditionat (pe conditie Evalueaza C2 Salt conditionat (pe conditie . . . . . . . . . . . . . . . Evalueaza Cn Salt conditionat (pe conditie ; ; Ramura_if ; jmp et_2 et_1: ; ; Ramura_else ; et_2: falsa) la et_1 falsa) la et_1 . . falsa) la et_1

S considerm, de exemplu, secvena pseudo-cod: if (car >= '0' AND car <= '9') { sir[i] = car - '0'; i = i + 1; } n care presupunem c variabila car se afl n AL, iar indicele i n registrul BX. Implementarea este urmtoarea: cmp jb cmp ja sub mov inc et_1: al, '0' et_1 al, '9' et_1 al, '0' sir [bx], al bx ; ; ; ; ; ; ; Evaluare al >= '0' Salt pe cond. falsa (al < '0') Evaluare al <= '9' Salt pe cond. falsa (al > '9') Calcul car - '0' Depunere in sir [i] Incrementare I

Al doilea caz de baz este cel n care subcondiiile sunt conectate prin operatorul OR. S considerm condiia compus: C = C1 OR C2 OR C3 OR ... OR Cn

i aceeai form de decizie compus: if (C) ; Ramura_if else ; Ramura_else Implementarea este urmtoarea: Evalueaza C1 Salt conditionat (pe conditie Evalueaza C2 Salt conditionat (pe conditie . . . . . . . . . . . . . . . Evalueaza Cn Salt conditionat (pe conditie ; ; Ramura_else ; jmp et_2 et_1: ; ; Ramura_if ; et_2: adevarata) la et_1 adevarata) la et_1 . . adevarata) la et_1

Cele dou abloane de implementare pentru condiii compuse de tip AND i OR se bazeaz pe proprietile elementare ale operaiilor logice respective. Astfel, la operaia AND, e suficient ca un singur operand (o subcondiie) s fie fals, pentru ca ntreaga condiie s fie fals. Similar, la operaia OR, e suficient ca un singur operand s fie adevrat, pentru ca ntreaga condiie s fie adevrat. A treia schem de dezvoltare se refer la implementarea condiiilor negate. O secven pseudo-cod de forma: if (NOT condiie) ; Ramura_if else ; Ramura_else se implementeaz la fel cu schema if-else obinuit, dar cu inversarea saltului condiionat: Evalueaza conditie Salt conditionat (pe conditie adevarata) la et_1 ; ; Ramura_if ; jmp et_2 et_1: ; ; Ramura_else ; et_2: Cu aceste trei operaii de baz (AND, OR, NOT), putem acum evalua orice tip de condiie logic. S considerm secvena pseudo-cod: if (C1 AND C2) OR C3) ; Ramura_if else ; Ramura_else Considerm subcondiiile C1 AND C2 i C3 i aplicm pentru nceput ablonul de la operaia OR: Evalueaza (C1 AND C2)

Salt conditionat (pe conditie adevarata) la et_1 et_3: Evalueaza (C3) Salt conditionat (pe conditie adevarata) la et_1 ; ; Ramura_else ; jmp et_2 et_1: ; ; Ramura_if ; et_2: Detaliem acum evaluarea subcondiiei (C1 AND C2), observnd c se sare la eticheta et_1 dac ambele subcondiii C1 i C2 sunt adevrate; altfel se sare la eticheta et_3: Evalueaza C1 Salt conditionat (pe conditie falsa) la et_3 Evalueaza C2 Salt conditionat (pe conditie adevarata) la et_1 et_3: Evalueaza (C3) Salt conditionat (pe conditie adevarata) la et_1 ; ; Ramura_else ; jmp et_2 et_1: ; ; Ramura_if ; et_2: Dup aceste modele, se pot evalua pas cu pas condiii orict de complicate. abloanele de evaluare se utilizeaz i la celelalte operaii din programarea structurat. 5.2 Cicluri cu test la partea superioar i inferioar Ciclul cu test la partea superioar, descris n pseudo-cod prin: while (conditie) ; Bloc se implementeaz dup ablonul: et_1: Evalueaza conditie Salt conditionat (pe conditie falsa) la et_2 ; ; ; Bloc jmp et_1 S considerm secvena urmtoare n limbajul C: while (*s >= '0' && *s <= '9') { n = 10 * n + *s - '0'; s++; } unde s este un pointer la char, iar n un ntreg. Aceast secven este tipic pentru conversia ASCII-ntreg. Presupunem variabila n asignat n AX, iar pointerul s asignat (ca adres near) n registrul SI i aplicm ablonul de implementare:

et_1: Evalueaza ( [SI] >= '0') Salt pe conditie falsa la et_2 Evalueaza ( [SI] <= '9') Salt pe conditie falsa la et_2 AX = AX * 10 + [SI] - '0' SI = SI + 1 jmp et_1 et_2: n codificarea propriu-zis, se va ncrca [si] n registrul al pentru o operare mai eficient: mov bx, 10 et_1: mov cl, [si] xor ch, ch cmp dl, '0' jb et_2 cmp dl, '0' ja et_2 mul bx sub cl, '0' add ax, cx inc si jmp et_1

; ; ; ; ; ; ; ; ;

CX <-- caracterul de la adresa SI Prima subconditie Salt pe conditie falsa A doua subconditie Salt pe conditie falsa n = n * 10 *s - '0' Valoare finala n s++

Ciclurile cu test la partea inferioar, descris n pseudo-cod prin una din formele: do ; Bloc while (conditie) se implementeaz dup abloanele: et: ; Bloc Evalueaza conditie Salt pe conditie adevarata la et et: ; Bloc Evalueaza conditie Salt pe conditie falsa la et repeat ; Bloc until (conditie)

S considerm un algoritm tipic pentru conversia ntreg-ASCII. descris n limbajul C: do { *s++ = n % 10 + '0'; n = n/10; } while (n != 0); *s = 0; Prin mpriri succesive la 10 se genereaz cifrele corespunztoare ntregului n i se depun n irul de caractere s (cifrele rezult n ordine invers). Implementarea este urmtoarea (considerm n memorat n registrul AX, iar pointerul n n registrul index Dl): mov et: xor div add mov inc test jnz mov dx, dx bx dl, '0' [di], dl di ax, ax et byte ptr [di],0 ; ; ; ; ; ; ; ; Deimpartit = DX:AX AX = n / 10, DX (DL) = n % 10 n % 10 + '0' Depunere in *s si apoi incrementare s Compara n cu 0 Reluare ciclu *s = 0 bx, 10

Un caz particular de ciclu cu test la partea superioar este ciclul cu contor ascendent, descris n pseudo-cod prin: for (contor = vi to vf step pas) ; Bloc n care se consider c pas este o valoare strict pozitiv. Dac specificaia pasului lipsete, se consider implicit pasul 1. Aceast descriere se poate detalia ntr-un ciclu de tip while i o iniializare: contor = vi; while (contor <= vf) { ; Bloc contor = contor + pas; } ceea ce arat c se poate aplica ablonul de implementare de la ciclul while. Ciclul cu contor descendent, descris n pseudo-cod prin: for (contor = vi downto vf step pas) ; Bloc n care, de asemenea, se consider c pas este pozitiv i implicit 1. i aceast form se poate detalia n: contor = vi; while (contor >= vf) { ; Bloc contor = contor - pas; } S considerm, de exemplu, o secven tipic de translatare la dreapta cu o poziie a elementelor unui tablou de ntregi: for (i = 99 downto 1) TAB [i] = TAB [i-1]; Asignm variabila i la registrul SI. Trebuie observat c indicii variaz dup elementele tabloului, dar adresele variaz cu cte 2 octei la fiecare element. Pentru o implementare ordonat, vom varia variabila indice SI exact ca n specificarea algoritmului i o vom ajusta temporar prin nmulire cu 2 naintea accesrii elementelor tabloului. Detalierea ciclului este: i = 99; et_1: Evalueaza ( i >= 1) Salt pe conditie falsa la et_2 TAB [i] = TAB [i-1]; i = i 1; et_2: Dup aceast detaliere, implementarea devine de rutin: mov di, 99 et_1: cmp di, 1 jl et_2 shl di, 1 mov ax, TAB[si] mov TAB[si-2], ax shr di, 1 dec di et_2: ; i = 99; ; ; ; ; ; ; ; Evalueaza (i >= 1) Salt pe conditie falsa (i < 1) Temporar, DI <-- 2*DI TAB [i] TAB [i-1] Refacere DI i = i 1

Secvena de mai sus ar putea fi implementat i prin instruciuni specifice irurilor de cuvinte (se presupune c

registrele DS i ES indic segmentul n care este memorat tabloul TAB): lea lea std mov rep si, TAB [98*2] di, TAB [99*2] cx, 99 movsw ; ; ; ; ; Sursa initiala Destinatie initiala Sens descendent Numar iteratii Transfer

O alt form posibil, care permite generalizri interesante este copierea la nivel de octet: lea lea std mov rep si, TAB [98*2 + 1] ; incepem de la di, TAB [99*2 + 1] ; ultimul octet cx, 99*2 movsb ; Numar de octeti ; Copiere

n general, prelucrarea tablourilor de tip oarecare presupune nmulirea indicilor de acces cu numrul de octei ai tipului de baz al tabloului. n acest sens, iniializrile registrelor SI i Dl din ultima secven se pot generaliza astfel: lea si, TAB [98 * (TYPE TAB) + (TYPE TAB - 1)] lea di, TAB [99 * (TYPE TAB) + (TYPE TAB - 1)] Dac secvena de copiere propriu-zis se nlocuiete cu: mov cx, 99 * (TYPE TAB) rep movsb atunci se obine o secven care implementeaz algoritmul dat, indiferent de tipul tabloului. S considerm un tablou de structuri de tipul: MY_STRUC struc n dw ? text db ENDS

10 dup (0)

i definim un tablou de 20 de structuri: . data tab MY_REC 20 dup (< , >) Secvena de translatare se poate scrie: lea lea std mov rep si, tab [(LENGTH tab - 1) * (TYPE MY_REC) 1] di, tab [(LENGTH tab ) * (TYPE MY_REC) - 1] cx, (LENGTH tab - 1) * (TYPE MY_REC) movsb

prin care se poziioneaz Dl (destinaia) pe ultimul octet al ultimei nregistrri din tablou, iar SI (sursa) pe ultimul octet al penultimului element din tablou. Numrul de iteraii la nivel de elemente este numrul de elemente al tabloului, micorat cu o unitate; la nivel de octei, se nmulete aceast valoare cu dimensiunea unui element. O alt form posibil de scriere exploateaz legtura dintre operatorii LENGTH, TYPE i SIZE. De exemplu, iniializarea contorului CX s-ar mai putea scrie: mov cx, SIZE tab - TYPE tab n cazurile n care ciclul cu contor se poate descrie prin: for (contor = n downto 1) ; Bloc sau atunci cnd se repet de n ori o anumit operaie, iar valoarea curent a contorului nu conteaz, ciclul se poate

implementa prin instruciunea LOOP: mov cx, n et: ; Bloc loop et S considerm o secven de determinare a maximului i a minimului unui tablou de ntregi cu semn, definit prin: .data TABLOU n val_max val_min i_max i_min dw dw dw dw dw dw 100 dup(?) ($-TABLOU)/2 ? ? ? ?

Se dorete determinarea att a valorilor maxime i minime, ct i a indicilor pe care apar aceste elemente. Secvena se poate descrie n pseudo-cod prin: val_max = TABLOU [0] ; val_min = TABLOU [0] ; i_max = 0; i_min = 0; for (i = 1 to n-1) { if (TABLOU [i] > val_max) { val_max = TABLOU [i]; i_max = i; } else if (TABLOU [i] < val_min) { val_min = TABLOU [i]; i_min = i; } } Pentru implementare, vom presupune c adresa elementului TABLOU[i] este alocat n registrul BX. Se va incrementa direct aceast adres, iar ciclul va fi implementat printr-o instruciune loop. Indicele elementului curent se obine printr-o diferen ntre adresa curent (BX) i adresa de nceput a tabloului (SI) i o mprire la 2. .code lea bx, TABLOU mov si, bx aici_1: mov cx, n dec cx aici_2: mov ax, [bx] mov val_max, ax mov val_min, ax mov i_max, 0 mov i_min, 0 add bx, 2 et_1: mov ax, [bx] cmp ax, val_max jle et_2 mov val_max, ax push bx sub bx, si ; Adresa elementului TABLOU [0] ; Copiata si in SI ; Sunt n-1 iteratii ; ; ; ; ; ; ; ; ; ; ; val_max = TABLOU [0] val_min = TABLOU [0] i_max = 0 i_min = 0 Adresa elementului TABLOU [1] AX <-- TABLOU [i] Evalueaza (TABLOU [i] > val_max) Salt pe conditie negata val_max = TABLOU [i] Salvare adresa curenta Adr. curenta - adr. de inceput

shr bx, 1 mov i_max, bx pop bx et_2: cmp ax, val_min jge et_3 mov val_min, ax push bx sub bx, si shr bx, 1 mov i_min, bx pop bx et_3: add bx, 2 loop et_1

; / 2 ; = indicele i_max ; Refacere adresa curenta ; ; ; ; Evalueaza (TABLOU [i] < val_min) Salt pe conditie negata val_min = TABLOU [i] Similar

; Adresa elementului urmator ; Ciclu dupa CX

Se observ c implementarea ciclului prin instruciunea LOOP complic determinarea indicelui elementului curent. Dac s-ar fi implementat un ciclu ascendent obinuit, nlocuind secvena dintre etichetele aici_1 i aici_2 prin: aici_1: mov cx, 1 cmp cx, n jl et_4 aici_2: iar secvena de dup eticheta et_3 prin: et_3: add bx, 2 inc cx jmp et_1 et_4: atunci, la fiecare iteraie, indicele elementului curent ar fi fost disponibil n registrul CX, iar secvenele de determinare a indicilor i_max i i_min s-ar fi redus la simple transferuri de forma: mov i_min, cx Exemplul de mai sus arat c implementarea ciclurilor cu contor prin instruciunea LOOP, aparent mai simpl, poate conduce la complicaii n interiorul ciclului. Exist i cicluri cu mai multe puncte de ieire (o ieire normal i una sau mai multe ieiri forate), ca n secvena pseudo-cod urmtoare, n care prin break s-a marcat ieirea forat din ciclu: while (conditie_1) { ; Bloc_1 if (conditie_2) break; ; Bloc_2 { O asemenea situaie se implementeaz combinnd abloanele de la while i if: et_1: Evalueaza (conditie_1) Salt pe conditie falsa la et_2 ; Bloc_1 Evalueaza (conditie_2) Salt pe conditie adevarata la et_2 ; Bloc_2 jmp et_1 et_2:

5.3 Selecia. Tabele de salt sau de apel de proceduri Operaia de selecie se descrie n pseudo-cod prin: selecteaza (c) dintre { c1: Bloc_1; c2: Bloc_2; . . . . . . cn: Bloc_n; [default: Bloc_d;] } n care c1, c2, ..., cn sunt aa-numitele cazuri (case). Acestea sunt, de fapt, valori de acelai tip cu variabila c. Cazul default corespunde situaiei n care variabila c nu are nici una din valorile c1, c2, ..., cn i este opional. Blocurile de instruciuni Bloc_1, Bioc_2,..., Bloc_n pot fi i vide. Implementarea natural a seleciei pornete de la observaia c o asemenea operaie este echivalent cu o succesiune de decizii, dup cum urmeaz; if (c = c1) ; Bloc_1 else if (c = c2) ; Bloc_2 . . . . . . . . else if (c = cn) ; Bloc_n else ; Bloc_d Se pot aplica acum abloanele de implementare de la operaia de decizie, ceea ce nseamn comparaii succesive i salturi condiionate. Aceast soluie de implementare devine incomod atunci cnd numrul cazurilor este mare. O alt soluie de implementare, mult mai eficient, utilizeaz tabele de salt sau de apel de proceduri. S presupunem c blocurile de instruciuni sunt organizate n felul urmtor: et_1: ; Bloc_1 jmp et et_2 : ; Bloc_2 jmp et . . . . . et_n: ; Bloc_n jmp et et_d; ; Bloc_d et: deci ieirea din operaia de selecie se face pe la eticheta et. Ideea soluiei de implementare este definirea unei tabele de salt, iniializat cu punctele de intrare n blocurile de instruciuni (deci cu adresele etichetelor et_i) i calculul automat al adresei corespunztoare de salt, pe baza cutrii valorii curente c ntr-un tabel de cazuri posibile. Pentru a fixa ideile, presupunem variabila c i cazurile c1,c2, ..., cn ca fiind reprezentabile pe cte un octet; de asemenea, presupunem toate blocurile de instruciuni definite n acelai segment de cod; registrele DS i ES indic segmentul curent de date. Variabilele n i c au semnificaiile din descrierea operaiei (variabila de selecie, respectiv numrul de cazuri posibile). Definiia celor dou tabele este: .data case db c1, c2, c3, ..., cn tabjmp dw et_1, et_2, ..., et_n c db ? n dw ?

10

S considerm un exemplu concret. Se citete un caracter de la tastatur i, funcie de valoarea sa, se afieaz un mesaj la consol. Considerm numrul cazurilor posibile ca fiind 4, iar constantele de selecie ca fiind caracterele 'a', 'b', 'c' i 'd'. Citirea caracterelor se execut ntr-o bucl din care se iese la apsarea tastei Enter. Implementarea este urmtoarea: .model large include io.h .stack 1024 .data case db 'a', 'b', 'c', 'd' tabjmp dw et_1, et_2, et_3, et_4 n dw 4 .code start: init_ds_es ; Initialiazare DS si ES iar: getc ; Citire caracter in AL cmp al, cr ; Este Enter ? je gata ; Daca da, oprire lea di, case ; Adresa tabela de cazuri mov cx, n ; Numar de cazuri explicite cld ; Directie ascendenta repne scasb ; Cautare caz (AL) in tabela jne et_d ; Daca ZF = 0, inseamna ca nu s-a ; identificat nici un caz explicit ; deci este cazul default dec di ; S-a gasit un caz explicit ; DI este pozitionat pe octetul ; urmator, asa ca il decrementeaza lea bx, case ; Adresa tabela de cazuri sub di, bx ; Diferenta = deplasament, in ; gama: 0 ... n-1 shl di, 1 ; inmultire cu 2 (adrese pe word) jmp tabjmp [di] ; Salt indirect la cazul respectiv gata: exit_dos ; ; Blocurile de instructiuni ; care trateaza ; cazurile explicite si implicite ; et_1: putsi <cr, jmp et et_2: putsi <cr, jmp et et_3: putsi <cr, jmp et et_4: putsi <cr, jmp et et_d: putsi <cr, et: ; ; Punct de ; operatia ; lf, 'Cazul 1 (a)', cr, lf> lf, 'Cazul 2 (b)', cr, lf> lf, 'Cazul 3 (c)', cr, lf> lf, 'Cazul 4 (d)', cr, lf> lf, 'Cazul default', cr, lf> iesire din de selectie

11

jmp iar end start Cutarea n tabela case se face prin instruciuni cu iruri de caractere, dup modelul standard descris la aceste instruciuni. Din adresa elementului identificat n tabel se calculeaz indicele acestuia (de la 0 la n-1) i, prin nmulire cu 2, poziia corespunztoare din tabela de adrese de salt. Se execut apoi un salt indirect intrasegment. Alte variante posibile de implementare sunt: cazurile memorate pe mai mult de un octet - se definete tabela case n mod corespunztor, i se execut o secven de cutare explicit, dup modelul: for (i = 0 to n-1) if (case [i] = c) break; if (i < n) jmp tabjmp [i] else jmp et_d blocurile asociate cazurilor sunt n segmente de cod diferite - se definete tabela de salt cu adrese de tip fac, iar etichetele et_i i et_d se definesc cu directiva LABEL i atributul FAR; blocurile asociate cazurilor sunt implementate ca proceduri - se definete tabela de adrese iniializat cu numele procedurilor respective i se nlocuiete instruciunea de salt indirect cu una de apel indirect de procedur; punctul de ieire din operaia de selecie va fi cel imediat urmtor apelului indirect de procedur. 5.4 Transferul parametrilor ctre proceduri Proiectarea ordonat i sistematic a procedurilor este un punct cheie n dezvoltarea unui sistem de programe n limbaj de asamblare. Problemele de baz care trebuie urmrite sunt: transferul parametrilor; ntoarcerea rezultatelor; zone de date proprii procedurilor; controlul stivei; recursivitatea; proceduri cu numr variabil de parametri. Pentru fiecare din aceste probleme, exista tehnici sistematice de abordare, care vor fi prezentate n continuare. Prima problem se refer la transferul parametrilor ctre proceduri. Daca n limbajele de nivel nalt acest lucru este impus de sintaxa limbajului (se definete o list de parametri), n limbaj de asamblare exista multiple posibiliti de transfer. Aceasta este, de altfel, i cauza pentru care o proiectare nesistematic a procedurilor, poate conduce la programe greu de citit i neles, predispuse la erori si foarte greu de ntreinut. Pe de alt parte, o proiectare ngrijit a procedurilor, combinat eventual cu macroinstruciuni adecvate, care respect tehnicile standard de transmitere a parametrilor, contribuie esenial la dezvoltarea unor programe uor de neles i de ntreinut, asemntoare - din acest punct de vedere - programelor n limbaje de nivel nalt. 5.4.1 Tipuri de transfer (prin valoare sau prin referin) O prim chestiune care trebuie decis n proiectarea unei proceduri este tipul de transfer al parametrilor. Se pot utiliza dou asemenea tipuri: transfer prin valoare, care implic transmiterea coninutului unei variabile; transfer prin referin, care implic transmiterea adresei de memorie a unei variabile. Alegerea ntre aceste dou tipuri de transfer se poate face dup urmtoarele criterii: dac variabila care trebuie transmis nu este alocat n memorie, ci ntr-un registru, se va alege transmiterea prin valoare; structurile de date de volum mare (tablouri, structuri, tablouri de structuri etc.) vor fi transmise totdeauna prin referin; dac procedura trebuie s modifice o variabila parametru formal, care este alocat n memorie, se va alege transferul prin referin; (modificarea unui parametru formal din interiorul procedurii trebuie totui utilizat cat mai puin, deoarece este o cauza majora de erori; este preferabil un transfer prin valoare i ntoarcerea valorii modificate). Pentru a fixa ideile, s consideram o procedura pro_add, de tip near, care adun dou numere pe 32 de bii, ntorcnd rezultatul n perechea de registre DX:AX. Datele sunt memorate n variabilele n1 i n2, iar rezultatul n variabila rez: .data n1 dd 10000H

12

n2 rez

dd 20000H dd ?

S presupunem c transmitem parametrii prin registre, ceea ce nseamn c avem nevoie de 4 registre generale, de exemplu DX:AX pentru primul parametru i CX:BX pentru al doilea. Secvena de apel a procedurii este urmtoarea: .code mov ax, word ptr n1 mov dx, word ptr n1 + 2 ; DX:AX = primul parametru mov bx, word ptr n2 mov ex, word ptr n1 + 2 ; CX:BX = al doilea parametru call near ptr pro_add mov word ptr rez, ax ; Rezultat in DX:AX mov word ptr rez + 2, dx Procedura pro_add se detaliaza astfel: pro_add proc near add ax, bx adc dx, cx ret pro_add endp S considerm acum varianta transmiterii prin referin a parametrilor, n care se transmit ctre procedur adresele de tip near ale variabilelor n1 i n2, prin registrele SI i DI. Secvena de apel este: lea si, n1 lea di, n2 call near ptr pro_add mov mov word ptr rez, ax ; Rezultat in DX:AX mov word ptr rez + 2, dx Procedura se dezvolta n felul urmtor: pro_add proc mov ax, add ax, mov dx, adc dx, ret pro_add endp near [si] [di] [si+2] [di+2] ; ; ; ; Partea low din primul numar + partea low din al doilea Partea high din primul numar + partea high din al doilea

n esen, se vede ca transmiterea prin referin implic operaii de adresare indirect n interiorul procedurii. Un aspect important este salvarea si restaurarea registrelor implicate n transferul parametrilor, ca si a celor utilizate n interiorul procedurii. 5.4.2 Transfer prin registre Vom trece acum la analiza modalitilor efective de transfer a parametrilor, fie ei valori sau adrese. O prim modalitate este transferul prin registrele mainii. Avantajul acestei soluii este faptul c, n procedur, parametrii actuali sunt disponibili imediat. Pentru conservarea registrelor, acestea se salveaz n stiv nainte de apel i se refac dup revenirea din procedur. Secvena de apel este, deci, organizat dup ablonul: ; ; ; ; Salvare in stiva a registrelor implicate in transfer incarcare registre cu parametrii actuali Apel de procedura Refacere registre din stiva

Dezavantajele acestei modaliti sunt: numrul limitat de registre ale mainii - e posibil s existe registre ocupate sau pur si simplu sa fie mai muli parametri dect registre disponibile;

13

neuniformitatea metodei - nu exista o modalitate ordonat de transfer, fiecare procedur avnd propriile reguli de transfer. 5.4.3 Transfer prin zona de date n aceast variant, se pregtete anterior o zona de date i se transmite ctre procedur adresa acestei zone de date. n aceasta form, organizarea zonei de date i secvena de apel a procedurii pro_add din paragraful anterior este: .data zona n1 n2 rez .code lea call label dword dd 10000H dd 20000H dd ? bx, zona pro_add

Procedura pro_add se scrie acum n forma: pro_add proc mov ax, add ax, mov dx, adc dx, ret pro_add endp near [bx] [bx+4] [bx+2] [bx+6] ; ; ; ; Partea low din primul numar + partea low din al doilea Partea high din primul numar + partea high din al doilea

Pentru un acces comod la zona de parametri, se recomanda definirea unei structuri care sa descrie organizarea zonei de date: TIP_ZONA nr1 nr2 rez TIP_ZONA struc dd ? dd ? dd ? ends

.data zona TIP_ZONA <10000, 20000, ?> .code lea bx, zona call pro_add Procedura pro_add se scrie astfel: pro_add mov ax, add ax, mov dx, proc near [bx].nr1 [bx].nr2 [bx].nrl+2 ; ; ; ; ; ; Partea low din primul numar + partcea low din al doilea Partea high din primul numar + partcea high din al doilea

adc dx, [bx+6].nr2+2 ret pro_add endp

5.4.4 Transfer prin stiv. Descrcarea stivei Transferul parametrilor prin stiva este cea mai important modalitate de transfer. Avantajele acestei metode sunt uniformitatea (se asigur un mecanism unic de transfer pentru toate procedurile) i compatibilitatea cu limbajele de nivel nalt (majoritatea compilatoarelor utilizeaz aceasta metoda). Transferul prin stiva este obligatoriu n situaia n care aplicaia conine att module n ASM, ct i , module n limbaj de nivel nalt. n principiu, transferul prin stiv const n plasarea n stiv (prin instruciuni de tip PUSH) a parametrilor, nainte de apelul procedurii. Astfel, procedura va gsi parametrii n stiv, imediat dup adresa de revenire. Problemele de baza care trebuie avute n vedere la implementarea acestui tip de transfer sunt: tipul procedurii (FAR sau NEAR); tipul parametrilor, n special al celor de tip adresa (FAR sau NEAR);

14

ordinea de plasare a parametrilor n stiva; accesul la parametri din interiorul procedurii; descrcarea stivei (de ctre programul apelant sau de ctre procedur). Tipul procedurii, tipul parametrilor i ordinea lor sunt importante pentru calculul deplasamentelor n stiv i pentru accesul corect la parametri. Tehnica de acces standard la parametrii procedurii se bazeaz pe adresarea bazat (eventual i indexat) prin registrul BP, care presupune registrul SS ca registru implicit de segment. Accesul se realizeaza prin operaiile urmtoare, efectuate chiar la intrarea n procedur: se salveaz BP n stiv; se copiaz SP n BP; se salveaz n stiv (eventual) registrele utilizate de procedur; se acceseaz parametrii prin adresare indirecta cu BP. La ncheierea procedurii, se execut operaiile urmtoare: se refac registrele salvate; se reface BP; se revine n programul apelant prin RET. S considerm aceeai procedur pro_add (de data aceasta de tip FAR), implementat prin aceast tehnic. Secvena de apel va fi: .data n1 dd 10000H n2 dd 20000H rez dd ? .code push word ptr n1+2 push word ptr n1 push word ptr n2+2 push word ptr n2 call far ptr pro_add add sp, 8 mov word ptr rez, ax mov word ptr rez+2, dx

; Partea high la adrese mari ; Partea low la adrese mici ; Similar pentru n2 ; ; ; ; Apel Descarcare stiva Depunere rezultat

Plasarea datelor n stiv trebuie s in seama de modul de reprezentare n memorie. Astfel, numerele pe 32 de bii se plaseaz n stiv n aa fel nct la adrese mici s se gseasc partea mai puin semnificativ. De asemenea, se observ descrcarea stivei (refacerea registrului SP la valoarea dinaintea secvenei de apel), prin adunarea explicita la SP a numrului de octei care a fost plasat n stiv. Pentru a accesa corect parametrii n stiv, este bine sa figurm imaginea stivei dup intrarea n procedur i salvarea registrului BP, innd cont de tipul procedurii, de numrul, tipul i ordinea parametrilor n stiv. Aceasta imagine este ilustrat n Figura 5.1 (reamintim c, n reprezentarea grafic a spaiului de memorie, adresele cresc de sus n jos). Procedura pro_add se scrie n felul urmtor: pro_add proc push bp mov bp, mov ax, add ax, mov dx, adc dx, pop bp ret pro_add endp far sp [bp+10] [bp+6] [bp+12] [bp+8] ; ; ; ; ; ; ; ; Secventa tipica de acces n1 low n2 low n1 high n2 high Refacere bp Revenire

Calculul explicit al deplasamentelor parametrilor (de tipul [bp+8], [bp+10] etc.) reprezint o surs potenial de greeli. n plus, ntreinerea procedurii este foarte greoaie. Dac se schimb tipul procedurii din FAR n NEAR sau dac se schimb ordinea celor doi parametri, toate liniile de program care conin deplasamente de acest gen trebuie rescrise.

15

Figura 5.1 Imaginea stivei la intrarea n procedura pro_add Aceste probleme se rezolv elegant prin definirea unei structuri ablon care s conin imaginea stivei, de la registrul BP n jos. Dac am figurat grafic imaginea stivei, definirea structurii ablon este imediat (vezi Figura 5.1): sablon_1 struc _bp _cs_ip n2_low n2_high n1_low n1_high sablon_1 ends dw dw dw dw dw dw ? 2 dup (7) ? ? ? ? ; BP ; Adresa de revenire ; ; Parametri ; ;

Procedura se rescrie acum n forma: pro_add proc far push bp mov bp, mov ax, add ax, mov dx, adc dx, pop bp ret pro_add endp

sp [bp].n1_low [bp].n2_low [bp].n1_high [bp].n2_high

ceea ce este mult mai clar dect versiunea anterioar. n plus, dac se modific tipul procedurii sau ordinea parametrilor, nu trebuie modificat dect definiia structurii ablon; asamblorul va calcula corect noile deplasamente. n forma de mai sus, procedura pro_add corespunde unei funcii C cu prototipul: long _pro_add (long x1, long x2); Sa presupunem acum ca procedura este fr tip (nu ntoarce nimic), dar n lista de parametri se transmite adresa rezultatului, ceea ce ar corespunde unui prototip C de forma: void _pro_add(long x1, long x2, long *adr_rez); Dac presupunem c adresa rezultatului este de tip FAR, secvena de apel a procedurii va fi: push ax ; Salvare temporara AX

16

push push push push mov push mov push call add

word ptr n1+2 word ptr n1 word ptr n2+2 word ptr n2 ax, SEG rez ax ax, OFFSET rez ax far ptr pro_add sp, 12

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Aici incepe secventa de apel Partea high la adrese mari Partea low la adrese mici Similar pentru n2 Adresa de segment La 286 se poate direct: push SEG rez Offset Apel Descarcare stiva Aici se termina secventa de apel Refacere AX

pop ax

Plasarea unor adrese FAR n stiva trebuie s in seama de modul de reprezentare al pointerilor de tip FAR (definii de exemplu prin directiva Define DoubleWord): la adrese mici se memoreaz offset-ul, iar la adrese mari, adresa de segment. Dac este cazul, se salveaz n stiv registrele utilizate n secvena de apel (n cazul de fata, AX). Structura de tip ablon de acces se rescrie, adugnd noul parametru de tip adres: sablon_2 struc _bp _cs_ip adr_rez n2_low n2_high n1_low n1_high sablon_2 ends dw dw dd dw dw dw dw ? ; BP 2 dup (7); Adresa de revenire ? ; Adresa rezultat (FAR) ? ; ? ; Parametri ? ; ? ;

n aceast variant, procedura pro_add este: pro_add proc far push bp mov bp, sp push ax push bx push es les mov add mov mov adc mov bx [bp].adr_rez ax [bp].n1_low ax [bp].n2_low es:[bx], ax ax [bp].n1_high ax [bp].n2_high es:[bx+2], ax ; Secventa tipica de acces ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Salvari registre utilizate Pointer la rezultat Calcul parte low Depunere rezultat low Calcul parte high Depunere rezultat high Refacere registre Refacere BP Revenire

pop es pop bx pop ax pop bp ret pro_add endp

n procedur nu putem face presupuneri despre segmentul n care este definit variabila rezultat. Ca atare, pentru a o accesa, utilizm perechea de registre ES:BX, ncrcat cu adresa parametrului, preluat din stiv. Aici apare necesitatea

17

ca datele de orice fel (n cazul de fa adresa FAR a rezultatului) s fie reprezentate n stiv n acelai fel ca n memoria de date. Dac nu am fi respectat convenia de reprezentare a adreselor FAR (offset-ul la adrese mici), instruciunea LES BX nu ar fi ncrcat corect adresa n perechea de registre ES:BX. Accesul la variabila rezultat este ilustrat n Figura 5.2. n toate exemplele de mai sus, descrcarea stivei a fost fcut de ctre programul apelant, printr-o instruciune ADD SP. Este posibil i varianta n care descrcarea stivei se face de ctre procedur. Acest lucru se implementeaz printr-o instruciune return de forma: ret N unde N este numrul octeilor care au fost pui pe stiva ca parametri. Evident, n acest caz nu se mai scrie instruciunea ADD SP n programul apelant.

Figura 5.2 Transmiterea unei adrese FAR ca parametru Problematica transferului parametrilor prin stiv trebuie cunoscut n amnunt atunci cnd interfam module ASM cu module scrise n limbaje de nivel nalt. Este posibil ca diverse proprieti s difere de la limbaj la limbaj sau chiar de la compilator la compilator. Spre exemplu, compilatoarele Borland C utilizeaz urmtoarea tehnic de transfer a parametrilor: parametrii sunt evaluai i plasai n stiv n ordinea invers a listei de argumente a funciei, adic primul parametru din lista este n vrful stivei, imediat dup adresa de revenire; stiva este descrcat de programul apelant. n schimb, compilatoarele Borland Pascal lucreaz exact pe dos: parametrii sunt plasai n stiv n ordinea din lista de argumente a procedurii (funciei), adic ultimul parametru din lista este n vrful stivei, imediat dup adresa de revenire; stiva este descrcat de programul apelant. Imaginea stivei la intrarea ntr-o procedura C, respectiv Pascal de forma: void f_C (int par_a, int par_b, int par_c); procedure f_Pascal (int par_a, par_b, par_c) este ilustrat n Figura 5.3.

18

Figura 5.3 Transferul parametrilor in C si Pascal Secvenele de apel ale celor doua proceduri s-ar scrie: push par_c push par_b push par_a call f_C add sp, 6 respectiv: push push push call par_a par_b par_c f_Pascal ; ; Secventa de apel ; pentru o procedura ; (functie) Pascal ; ; Secventa de apel ; ; pentru o functie C ;

5.5 ntoarcerea datelor de ctre proceduri Procedurile care ntorc valori corespund funciilor din Pascal sau funciilor cu tip nevid din C. n limbaj de asamblare, ne punem problema n sens mai larg, anume ce modaliti exist pentru a furniza un rezultat programului apelant (inclusiv prin tehnici neortodoxe). Aceste modaliti sunt: a) n lista de parametri apar adresele rezultatelor sau adresa unei zone de date care conine cmpuri pentru rezultate; b) rezultatele se ntorc prin registre; c) rezultatele se ntorc n vrful stivei. Tehnica a) a fost deja descris la transmiterea parametrilor prin zona de date sau prin stiv. Practic, n interiorul procedurii se depun explicit rezultatele la adresele coninute n parametrii formali respectivi. Dei aceasta tehnic nu este recomandat ca model n programarea structurat, ea nu se poate evita atunci cnd rezultatele au dimensiuni mari i sunt prin natura lor gestionate prin adrese. Un exemplu clar este cel al irurilor de caractere sau al tablourilor n general. Mai mult dect att, compilatoarele de nivel nalt utilizeaz aceast tehnic (n mod transparent pentru utilizator) atunci cnd trebuie ntoarse tipuri de volum mare prin numele funciei. Concret, se transmite ctre funcie adresa unei zone temporare de date (ca parametru suplimentar al funciei), n care s se depun rezultatul. Tehnica b) este folosit cel mai frecvent. De obicei, se folosete registrul acumulator, eventual extins (adic AL, AX, respectiv DX:AX, dup cum rezultatul este pe 1, 2 sau 4 octei). Dezavantajul acestei tehnici este limitarea la 32 de bii a tipului de date returnat. Totui, compilatoarele Borland o utilizeaz ca metod standard de returnare a tipurilor de date de maxim 32 de bii. Tehnica c) se folosete destul de rar, fiind total nestandard. Const n plasarea rezultatelor n vrful stivei din momentul revenirii n programul apelant. Aceasta nseamn c, practic, rezultatele se suprapun n stiv peste parametrii de apel (se descarc implicit stiv) i chiar peste adresa de revenire, ceea ce face foarte complicat scrierea procedurii. Vom prezenta totui aceast tehnic pentru c este un exemplu de operaie complex asupra stivei. Considerm ca de obicei procedura pro_add, de tip far. Parametrii se plaseaz n stiv n ordinea n1, n2. Secvena de apel este: push push push push call word ptr n1+2 word ptr n1 word ptr n2+2 word ptr n2 far ptr pro_add

; in acest moment, in

19

pop mov pop mov

ax word ptr rez, ax ax word ptr rez + 2, ax

; varful stivei se ; gaseste rezultatul ; adunarii ; Preia ; rezultat ; si descarca ; stiva

tim acum ce ar trebui s execute procedura pro_add. Pentru a vedea exact ce aciuni trebuie implementate, se pornete de la coninutul stivei n momentul intrrii n procedur i de la cum trebuie s arate stiva nainte de instruciunea de revenire (RET) din procedura. Aceste doua situaii sunt ilustrate n Figura 5.4.

Figura 5.4 ntoarcerea rezultatelor n vrful stivei Evident, procedura trebuie s construiasc imaginea stivei din momentul revenirii n programul apelant, iar secvena de apel trebuie s readuc registrul SP la valoarea iniiala. Pornind de la aceste considerente, definim structuri de acces la stiva conform ablonului de la intrare, respectiv de la ieire. Pentru ablonul de intrare utilizam structura sablon_3: sablon_3 struc _bp_3 _ip_3 _cs_3 n2_low n2_high n1_low n1_high sablon_3 ends dw dw dw dw dw dw dw ? ? ? ? ? ? 7 ; ; ; ; ; ; ; BP Offset revenire Segment revenire Parametri

pentru ablonul de ieire, definim structura sablon_4: sablon_4 struc _bp_4 _ip_4 _cs_4 rez_low rez_high sablon_4 ends dw dw dw dw dw dw dw ? ? ? ? ? ? ?

Campurile _bp_3 si _bp_4 se gasesc in aceeasi pozitie din memorie. S-au utilizat nume diferite, deoarece sintaxa

20

structurilor impune acest lucru. Din imaginile stivei din Figura 5.4 rezulta si operatiile care se executa. Dupa calculul sumei, adresa de revenire va trebui deplasata cu 4 octeti Tn jos Tn stiva, rezultatui se va depune dupa noua pozitie a adresei de revenire, se va pozitiona registrul SP pe adresa de revenire si se va executa RET. Procedura pro_add se implementeaza astfel: pro_add proc far push bp mov bp, sp push ax push dx mov ax, [bp].n1_low add ax, [bp].n2_low mov dx, [bp].n1_high adc dx, [bp].n2_high mov [bp].rez_low, ax mov [bp],rez_high, dx mov mov mov mov pop pop pop add ax, [bp]._ip_3 [bp]._ip_4, ax ax, [bp]._cs_3 [bp]._cs_4, ax dx ax bp sp, 4

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Salvare registre folosite Calcul rezultat Depunere rezultat conform sablonului de iesire Pregatire offset adresa de revenire Pregatire segment adresa de revenire Refacere registre folosite Refacere BP Pozitionare SP pe adresa de revenire

ret pro_add endp n unele situaii, este posibil ca zona de stiv n care se depun rezultatele s fie suprapus peste vechea zon n care se gsete adresa de revenire. n acest caz, se plaseaz nti adresa de revenire n noua poziie din stiva i apoi se depune rezultatul. 5.6 Proceduri cu zone de date proprii (variabile locale) n multe cazuri, nu putem aloca toate variabilele dintr-o procedur n registrele procesorului. n aceast situaie, procedura trebuie s utilizeze variabile locale proprii, altele dect parametrii formali i dect variabilele alocate n registre. Evident, se pune problema zonelor de memorie n care s fie alocate aceste variabile locale. Exist trei modaliti de baz pentru aceasta alocare: n segmentul de date global (.data) vizibil din toate modulele, n stiv sau ntr-un segment de date propriu. Prima modalitate, care este evident, contrazice, de fapt, caracterul local al acestor variabile i nu se recomand a fi folosit. Vor fi, deci, detaliate celelalte dou metode. 5.6.1 Variabile locale definite in stiv. Ca i tehnica de transmitere a parametrilor prin stiv, aceast metod este standardizat, fiind de fapt o extensie a tehnicii de transfer prin stiv. Operaiile care se execut la intrarea n procedur sunt urmtoarele: se salveaz BP n stiv; se decrementeaz registrul SP cu numrul necesar de octei pentru variabilele locale; se copiaz SP n BP; se salveaz (eventual) registrele folosite n procedur; se acceseaz parametrii formali i variabilele locale conform ablonului stivei. n secvena de ieire din procedur, se execut urmtoarele operaii: se refac registrele salvate; se incrementeaz SP cu acelai numr de octei cu care a fost incrementat n secvena de intrare; se reface registrul BP din stiva; se revine n programul apelant (eventual cu descrcarea stivei de parametri. Pentru exemplificare, sa consideram o procedura pro_local, de tip NEAR care are doi parametri formali de tip WORD i trei variabile locale, toate de tip word. Presupunem c stiva este descrcat de parametrii formali de ctre procedur. Conform operaiilor de mai sus, definim o structura ablon pentru accesul n stiva: sablon struc loc_1 dw ? loc_2 dw ?

21

loc_3 dw _bp dw _ip dw par_2 dw par_1 dw sablon ends

? ? ? ? ?

Schema de dezvoltare a procedurii pro_local este: pro_local proc near push bp sub sp, 6 mov bp, sp ; ; ; ; ; ; ; ; ; ; ; ; ; Salvare BP Spatiu pentru variabile locale Acces prin BP Acces la parametrii fonnali prin expresiile [bp]. par_l, [bp].par_2 Acces la variabilele locale prin expresiile [bp].loc_l, f [bp].loc_2, [bp].loc_3 Refacere spatiu local Refacere BP

add sp, 6 pop bp ret 4 pro_local endp

; Revenire cu descarcare Imaginea stivei dup secvena de intrare n procedur este ilustrat n Figura 5.5.

Figura 5.5 Variabile locale alocate in stiv Variabilele alocate n stiv au unele caracteristici care deriv din metoda de alocare: spaiul de memorie alocat nu exista dect pe durata procedurii; variabilele nu au alocate adrese fixe de memorie: adresele depind de poziia curent a stivei; variabilele nu-i pstreaz valoarea de la un apel la altul. Cea de-a doua proprietate este mai important, deoarece exista situaii n care se dorete n mod explicit ca o variabila local s-i pstreze valoarea de la un apel la altul al procedurii. n acest caz, e obligatorie alocarea n segmente proprii de date, deci la adrese fixe. 5.6.2 Variabile locale alocate static Alocarea variabilelor n segmente proprii de date se mai numete i alocare static, deoarece variabilele au asociate adrese fixe de memorie.

22

Ca mod practic de implementare, se definete un segment de date local cu ajutorul directivei SEGMENT, ceea ce previne gruparea acestui segment cu alte segmente. Accesul la segmentul local se va realiza prin unul din registrele DS sau ES, urmnd ca celalalt registru de segment de date sa fie utilizat (dac este cazul) pentru accesul la segmentul de date al programului apelant. S consideram aceeai procedur pro_local, de data aceasta de tip FAR, cu trei variabile locale si doi parametri formali. Definim segmentul local prin: local_data loc_1 loc_2 loc_3 local_data segment dw ? dw ? dw ? ends

i convenim s accesam acest segment prin registrul ES. Ca atare, definiia procedurii pro_local va fi ncadrat de directive ASSUME corespunzatoare. Parametrii formali sunt transmii prin stiva, conform ablonului de acces: sablon struc _bp dw _cs_ip dd par_2 dw par_1 dw sablon ends ? ? ? ?

ASSUME es:local_data pro_local proc far push bp mov bp, sp push es mov es, SEG local_data ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; pop es pop bp ret 4 pro_local endp ASSUME es:nothing Exist situaia n care unul sau mai muli parametri formali sunt pointeri (adrese) far. n acest caz, se salveaz unul din registrele DS si ES n stiv i se ncarc pointerul respectiv ntr-o pereche adecvat de registre (cu instruciunile LDS sau LES). S presupunem, de exemplu, ca unul din parametrii formali, descris n ablonul de acces prin par_far, indic un cuvnt care trebuie copiat n variabila locala loc_1. Secvena de copiere va fi: push ds lds bx, [bp].par_far mov ax, [bx] ; Salvare ; incarcare pointer ; Acces DS : asa cum vine din programul apelant ES : pozitionat pe segmentul local Acces la parametrii formali prin expresiile [bp]. par_1, [bp].par_2 Acces la variabilele locale prin expresiile loc_1, loc_2, loc_3 sau, mai clar, es:loc_1, es:loc_2, es:loc_3

23

mov es:loc_1, ax pop ds

; Copiere in segment local

Dac unul din parametri este o adresa far de procedur, se poate executa direct un apel indirect, prin: call dword ptr [bp].par_far S considerm un exemplu n care utilizarea variabilelor locale statice este obligatorie, anume un generator simplu de numere aleatoare pe 16 bii, implementat prin procedura de tip FAR rand_asm. Procedura are ca parametru un ntreg pe 16 bii fr semn (notat generic n) si ntoarce n AX un numr aleator fr semn n domeniul 0...n-1. Metoda de generare se bazeaz pe o variabila locala X, asupra creia se execut operaia: X <- partea mediana (X*X + C) unde X*X este ptratul lui X (pe 32 de bii), iar C este o constanta pe 32 de bii. Prin parte median se neleg biii 8...23 din cei 32 de bii ai expresiei calculate (vezi Figura 5.6). Dup ce s-a calculat noua valoare a lui X, se ntoarce programului apelant valoarea X MOD n, deci un numr ntre 0 i n-1. Toi operanzii se considera fr semn. n astfel de operaii, trebuie analizat cu atenie problema depirilor. Calculul X mod n se face printr-o mprire a lui X la n, unde att X, ct i n sunt pe 16 bii. Se va executa de fapt o mprire DX:AX la n, cu DX = 0, fapt care asigur evitarea depirii: cea mai mare valoare posibila a lui X (65535), mprit la cea mai mic valoare nebanal a lui n (2) nu produce depire. Caracterul pseudo-aleator al algoritmului de mai sus provine n mod esenial din faptul c variabila X i pstreaz valoarea de la un apel la altul al procedurii (vechea valoare a lui X participa la calculul noii valori). Pentru accesul la parametrul n, se utilizeaz o structur ablon.

Figura 5.6 Un generator simplu de numere aleatoare Conform metodei de dezvoltare expuse mai sus, definim un segment local n care rezervm spaiu pentru variabila de tip word X i pentru constanta de tip double-word C (ambele iniializate cu cte o valoare oarecare). Deoarece va trebui s executm o adunare pe 32 de bii, avem nevoie de acces explicit la prile low i high ale constantei C, pstrnd definiia ei ca variabil double-word. Acest lucru este realizat prin operatorul THIS, cu ajutorul cruia se definesc constantele simbolice c_lo i c_hi. Calculele vor folosi registrul AX, respectiv perechea de registre DX:AX, care este operand implicit la nmuliri i mpriri. Implementarea este urmtoarea (presupunem un fiier surs cu numele rand.asm): ; ; Fisier RAND.ASM ; .model large public rand_asm, init_asm sablon struc _bp dw ? _cs_ip dd ? n dw ? sablon ends ; sablon de ; de acces ; la stiva

24

local_data x c_lo c_hi c local_data

segment dw 111 equ this word equ c_lo + 2 dd 115321 ends

; Variabila locala statica X ; Constanta C

.code assume es:local_data rand_asm proc far push bp mov bp, sp pushf cmp [bp].n, 2 mov ax, [bp].n jb gata push es push dx mov ax, SEG local_data mov es, ax mov mul add adc mov mov mov xor div mov ax, es:x es:x ax, c_lo dx, c_hi al, ah ah, dl es:x, ax dx, dx [bp].n ax, dx

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Secventa standard de intrare Salvare bistabili Test caz banal (n = 0,1) Se intoarce chiar n Salvari registre folosite Pozitionare ES pe segmentul local Variabila X Ridicare la patrat (DX:AX) Adunare cu C Luarea partii mediane din DX:AX in AX Depunere in X Pregatire impartire impartire (0:X) la n Catul se pune in AX Refaceri registre

pop dx pop ea gata: popf pop bp ret rand_asm endp

; Refacere bistabili ; ; Secventa standard ; de iesire ; ; Initializare generator ;

init_rand proc far push bp mov bp, sp push es push ax mov ax, SEG local_data mov es, ax mov ax, [bp].n mov es:x, ax pop ax pop es pop bp ret init_rand endp end

; Salvari

; Copiere parametru ; in variabila X ; Refaceri

25

Procedura rand_asm este completata cu o rutina de iniializare a generatorului, avnd un parametru de tip word transmis prin stiv, care iniializeaz variabila X. Se folosete aceeai structur ablon pentru accesul la stiv. S consideram un exemplu de apel al acestor proceduri, pe care Ie presupunem definite n fiierul surs rand.asm. Dorim iniializarea cu valori aleatoare n gama 0...9999 a unui tablou de 256 de ntregi, pe care l afim, l sortm cresctor i apoi l afim din nou. Pentru afiare i sortare utilizm procedurile tipvec i bubble, dezvoltate n 2.7, pe care Ie presupunem definite ntr-un fiier surs cu numele bubble.asm, care conine declaraii PUBLIC ale procedurilor n cauz. n programul principal (presupus n fiierul surs main.asm), cele patru proceduri se declar ca simboluri externe i se folosesc conform metodei proprii de transfer al parametrilor: ; ; Fisier MAIN.ASM ; .model large include io.h extrn rand_asm: far, init_rand: far, tipvec: far, bubble: far .stack 1024 .data vector dw 256 dup (?) dim dw ($-vector)/2 .code start: init_ds_es mov ax, 1131 ; Valoare initiala push ax ; a generatorului call init_rand add sp, 2 ; Descarcare stiva ; mov cx, dim ; Dimensiune vector lea bx, vector ; Adresa vector reluare: mov ax, 10000 ; Domeniu 0...9999 push ax call rand_asm add sp, 2 ; Descarcare stiva mov [bx], ax ; Depunere valoare add bx, 2 ; Actualizare adresa loop reluare ; putsi <Vector nesortat, cr, lf> lea si, vector ; Adresa tablou mov cx, dim ; Numar de elemente call tipvec ; Afisare tablou nesortat ; lea bx, vector ; Adresa tablou mov cx, dim ; Numar de elemente call bubble ; Sortare tablou ; putsi <cr,lf,'Vector sortat', cr, lf> lea si, vector mov cx, dim call tipvec ; Afisare tablou sortat ; exit_dos ; Iesire in DOS end start Secvena de dezvoltare a acestei aplicatii este: > > > > tasm rand.asm tasm bubble.asm tasm main.asm tlink main rand bubble io, main

n urma creia rezult un fiier executabil main.exe.

26

Exemplul de mai sus constituie i un model de dezvoltare modular a unei aplicaii. Se observ c o proiectare ngrijit a modulelor permite refolosirea lor n diverse contexte. 5.7 Proceduri recursive n limbaj de asamblare, nu exist restricii asupra recursivitii: orice procedur Proc_a se poate apela pe ea nsi (recursivitate direct) sau poate apela o procedura proc_b care, la rndul ei apeleaz procedura proc_a (recursivitate indirect). n dezvoltarea unei proceduri recursive, trebuie pornit de la specificarea algoritmului recursiv. Specificarea unui asemenea algoritm pune n eviden un caz de baz (care oprete recursivitatea) i un caz general, n care se invoc acelai algoritm, aplicat altui set de date. Specificarea corect a cazului de baz i garantarea faptului c acesta este atins ntotdeauna, indiferent de setul de date de intrare primit, sunt punctele-cheie ale proiectrii unui algoritm recursiv. S considerm, ca exemplu, o procedur de afiare n baza 10 a unui numr pe 16 bii fr semn. La nivelul funciilor de intrare / ieire, avem posibilitatea de a afia caractere ASCII. Dorim deci un algoritm recursiv care s genereze cifrele zecimale ale numrului, n ordinea afirii. n 2.2.5 a fost dat un algoritm nerecursiv, care producea cifrele zecimale ale numrului n ordine invers. Algoritmul recursiv se specific n felul urmtor: putu_proc (n) { daca (n < 10) afiseaza (n + '0') altfel { putu_proc (n/10) afiseaza (n MOD 10 + '0') } } Cazul de baza este (n < 10), pentru care se face doar afiarea cifrei i revenirea n programul apelant. n Figura 5.7 sunt ilustrate apelurile recursive n cazul afirii numrului 123 i valorile succesive ale parametrului n.

Figura 5.7 Apelul recursiv al procedurii putu proc Regulile generate de implementare a procedurilor recursive sunt: fiecare apel al procedurii nu trebuie s afecteze datele apelurilor precedente (procedura s fie reentrant); nu se folosesc variabile locale alocate static, ci numai n stiv sau n registre; parametrii se transmit prin stiv (se asigur implicit faptul c parametrii sunt locali fiecrui apel); registrele ale cror valori au semnificaie de la un apel la altui se salveaz n stiv; dac procedura ntoarce valori prin registre, rezultatele intermediare trebuie memorate n stiv. Pentru implementarea procedurii putu_proc, se va utiliza funcia DOS 2, care afieaz la consol caracterul primit n registrul DL. Pentru accesul la stiv, considerm structura ablon: sablon struc _bp dw ? _cs_ip dw ? n dw ? sablon ends Pentru o implementare mai eficienta, rescriem algoritmul recursiv Tn forma: putu_proc (n)

27

{ daca (n < 10) x < n altfel { putu_proc (n/10) x <- n MOD 10 } afiseaza x + '0' } Alocarea variabilelor este urmtoarea: n - n stiva, accesibil prin expresia [bp].n x - n registrul DL (care se salveaz / restaureaz) Implementarea este urmtoarea: putu_proc proc far push bp mov bp,sp push dx push ax push bx ; ; ; ; ; ; ; ; ; push ax ; call far ptr putu_proc ; p_u_1 : add dl, "O" ; mov ah, 2 ; int 21H ; pop bx pop ax pop dx pop bp retf 2 pufcu_proc endp mov ax, [bp].n cmp ax, 10 mov dl, al jb p_u_1 mov bx, 10 xor dx, dx div bx ; ; ; ; ; Secventa standard Salvari registre folosite Test caz de baza x <-- n Salt la afisare Caz general Se calculeaza n/10 si n MOD 10 AX = n/10 DX (DL) = x = n MOD 10 Apel recursiv cu n/10 ca parametru x + '0' Cod functie DOS Afisare

; Refaceri ; registre ; Return cu descarcare

n implementarea de mai sus, este esenial integritatea variabilei x de la un apel la altul, deci salvarea / restaurarea registrului DX. Pentru a preveni depirile, se face mprirea 32 de bii la 16 bii, deci constanta 10 se ncarc ntr-un registru de 16 bii. Dup mprire, DX (de fapt DL) conine restul n MOD 10, iar AX ctul (n/10). Se pune, deci, AX n stiv i se apeleaz recursiv procedur. Dup revenire, se afieaz cifra din DL. Putem acum dezvolta o procedur care s afieze un numr pe 16 bii cu semn, dup algoritmul: puti_proc (n) { daca (n < 0) { n < -n afiseaza ('-') } putu_proc (n) }

28

Dac numrul e negativ, se complementeaz fa de 2 i se afieaz semnul '-', dup care se afieaz numrul complementat. Implementarea este urmtoarea: puti_proc push mov push push mov or jns mov mov int neg p_i_1: push call pop pop pop retf puti_proc proc far bp bp, sp ax dx ax, [bp].n ax, ax p_i_1 dl, '-' ah, 2 21H word ptr [bp].n [bp].n far ptr putu_proc dx ax bp 2 endp ; Secventa ; standard ; Salvari ; registre ; ; Parametrul n ; Pozitionare bistabili ; Test bit de semn ; Afisare ; semn '-' ; Complementare n ; ; ; ; ; Pregatire parametru Apel putu_proc Refaceri registre

; Return cu descarcare

Se observa forma directa de plasare a parametrului [bp].n n stiv, pentru apelul procedurii putu__proc. Putem scrie acum i macroinstruciuni de apel adecvate: puti macro X push X call far ptr puti_proc endm putu macro X push X call far ptr putu_proc endm Dup modelul procedurii putu_proc, putem scrie o procedur care s afieze la consola reprezentarea unui numr pe 16 bii fr semn ntr-o baz de numeraie data. Baza se consider n domeniul 2...36, iar cifrele ntr-o baza mai mare ca 10 sunt '0'...'9', 'A', 'B' etc. Limitarea bazei la valoarea 36 deriva din aceasta alegere a cifrelor. Algoritmul este urmtorul: put_base (n, baza) { daca (baza < 2 sau baza > 36) return daca (n < baza) x <- n altfel { put_base (n/baza, baza) x < n MOD baza } daca (x < 10) afiseaza (x + '0') altfel afiseaza (x + "A" - 10) } Se ncepe printr-un test de corectitudine a bazei: dac aceasta nu este n domeniul admisibil, se revine imediat n programul apelant. Se execut apoi acelai algoritm recursiv ca n procedura putu_proc, nlocuind constanta 10 cu variabila

29

baz. Afiarea cifrei curente x se face difereniat: dac este mai mica sau egala cu 10, se afieaz ca cifr zecimal; altfel, se afieaz ca litera. Pentru accesul la stiv, se utilizeaz structura ablon de mai jos: sablon _bp _cs_ip baza n sablon struc dw ? dd ? dw ? dw ? ends

Implementarea este urmtoarea (se considera c stiva este descrcat de programul apelant): put_base proc far push bp mov bp, sp cmp word ptr [bp].baza, 2 jb err_exit cmp word ptr [bp].baza, 36 ja err_exit ; push ax push dx mov ax, [bp].n cmp ax, [bp].baza mov dl, al jb p_b_1 xor dx, dx div [bp].baza ; Secventa standard ; Test baza corecta ; baza < 2 ; baza > 36 ; ; ; ; ; ; ; ; ; ; Salvari registre Test n < baza ? x <-- n Salt la afisare Calcul n/baza si n MOD baza AX = n/baza DX (DL) = n MOD baza

; ; Pregatire parametri pentru apelul recursiv ; push ax ; n/baza push [bp].baza ; baza call far ptr put_base ; Apel recursiv add sp, 4 ; Descarcare stiva p_b_1: mov ah, 2 ; Cod functie DOS cmp dl, 10 ; x < 10 ? jae p_b_2 add dl, '0' ; x + '0' jmp p_b_3 p_b_2: add dl, 'A' - 10 ; x + 'A' - 10 p_b_3: int 21H ; Afisare pop dx ; Refaceri pop ax ; registre err_exit: pop bp ret ; Revenire put_base endp Deoarece toate variabilele implicate sunt fr semn, se folosesc salturi condiionate de ,,below" i ,,above". Un mic program de test (listat n continuare) citete un numr i o baz de la consol i afieaz numrul n baza dat, dup care reia procesul de citire. Dac numrul introdus este 0, programul se termin. .model large include io.h .stack 1024

30

.code start: init_ds_es reluare: putsi <cr,lf,'Numar = '> getu or ax,ax jz exit push ax putsi <'Baza = '> getu push ax call far ptr put_base add sp, 4 jmp reluare exit: exit_dos end start

; ; ; ; ; ; ; ; ; ; ;

Prompter Citire intreg Test oprire ? Salt la iesire Plasare n in stiva Prompter Citire intreg Plasare baza in stiva Apel Descarcare etiva Reluare

Un caz deosebit l constituie funciile recursive, adic procedurile care ntorc valori. Trebuie acordat atenie memorrii valorilor ntoarse de apelurile recursive. S considerm o funcie recursiv care ntoarce numrul Fibonacci de ordinul n, definit recursiv prin: fib (0) = fib (1) = 1 fib (n) = fib (n-1) + fib (n-2), pentru n >= 2 Aceast funcie a fost evaluat prin iteraie n 2.4.4. Vom studia acum implementarea recursiv. Se observ c, pentru valoarea lui fib(n), sunt necesare dou apeluri recursive i salvarea rezultatului ntors de primul apel. Presupunem c parametrul n se transmite prin stiv, c procedura este de tip FAR, c ntoarce rezultatul n AX (ca numr fr semn) i c stiva este descrcat de programul apelant. ablonul de acces este definit prin structura: sablon struc _bp dw ? _cs_ip dd ? n dw ? sablon ends iar implementarea este urmtoarea: fib proc far ; ; Primeste in stiva numarul n ; Intoarce in AX valoarea fib (n) ; push bp mov bp, sp push bx ; Salvare registru folosit ; mov ax, [bp].n ; Preia argumentul n cmp ax, 1 ; Cazul de baza: n <= 1 jbe fib_1 ; Salt la evaluare imediata ; dec ax ; Pregatire apel push ax ; pentru n-1 call fib ; AX = fib (n-1) add sp, 2 ; Descarcare stiva mov bx, ax ; Rezultat intermediar ; mov ax, [bp].n ; Pregatire dec ax ; apel dec ax ; pentru push ax ; n-2

31

call fib ; AX = fib (n-2) add sp,2 ; Descarcare stiva ; ; in acest moment, AX = fib (n-2), BX = fib (n-1) ; add ax, bx ; Calcul fib (n) jmp fib_2 fib_1: mov ax, 1 ; Evaluare fib (0) si fib (1) fib_2: pop bx ; Restaurare pop bp ret ; Revenire fib endp n implementarea de mai sus, este eseniala salvarea i restaurarea registrului BX, care memoreaz rezultatul intermediar fib (n-1). Programul de test al acestei proceduri este lsat ca exerciiu pentru cititor. Nu putem ncheia discuia despre proceduri recursive fr a vorbi despre problema clasic a turnurilor din Hanoi. n aceast problem, se consider n discuri de diametre distincte, aezate pe un suport vertical (turn surs), n ordinea descresctoare a diametrelor (vezi Figura 5.8). Se cere s se mute ntreg ansamblul pe un alt turn (destinaie), folosind un turn de manevr. Discurile se pot muta unul cte unul, cu restricia c un disc nu se poate aeza dect deasupra unui disc de diametru mai mare.

Figura 5.8 Turnurile din Hanoi Algoritmul care rezolv aceast problem este n mod natural recursiv i se bazeaz pe observaia c, dac n = 1, adic exist un singur disc, acesta se poate muta direct pe turnul destinaie. Dac notm simbolic operaia de mutare prin: move (n, sursa, destinatie, manevra) unde surs, destinaie i manevr sunt cele trei turnuri, algoritmul se implementeaz prin: move (n, sursa, destinatie, manevra) { daca (n = 1) Muta discul de pe sursa pe destinatie altfel { move (n-1, sursa, manevra, destinatie) move (1, sursa, destinatie, manevra) move (n-1, manevra, destinatie, sursa) } } Se mut primele n - 1 discuri de pe turnul surs pe turnul manevr, folosind ca spaiu de lucru turnul destinaie. n acest moment, turnul surs conine un singur disc, turnul destinaie este liber, iar turnul manevr conine n - 1 discuri. Se mut unicul disc de pe turnul sursa pe turnul destinaie. n acest moment, turnul surs este liber, turnul destinaie conine discul cu diametrul cei mai mare, iar turnul manevra conine celelalte n - 1 discuri. Se aplica recursiv algoritmul, mutnd n - 1 discuri de pe turnul manevr pe turnul destinaie i folosind turnul surs ca spaiu de lucru. Pentru un n dat, se deplaseaz explicit discul cu diametrul cel mai mare, ceea ce asigur ndeplinirea restriciei din enun. Implementarea algoritmului se va face prin mesaje explicite la consol, turnurile fiind codificate prin caractere.

32

Implementarea algoritmului, mpreun cu un program de test, este listat n continuare. Structura ablon definete ordinea de plasare a parametrilor n stiv: n, surs, destinaie, manevr, toate de tip word. Toate apelurile recursive vor utiliza aceast ordine. Simularea mutrilor se face prin afiarea unui mesaj, definit n zona de date. Cmpurile sursa_asc i dest_asc sunt variabile i vor identifica (prin cte un caracter) turnurile respective. Cnd n = 1 (cazul de baz), se depun caracterele care identific turnurile n irul de caractere mesaj i se afieaz acest ir. Implementarea apelurilor recursive consta n plasarea n stiv a parametrilor n ordinea precizat i apelul procedurii. Programul principal citete ciclic de la consol numrul n, afieaz un mesaj de identificare, pregtete parametrii n stiv i apeleaz procedura move. Bucla se oprete cnd se introduce valoarea 0 pentru n. .model large include io.h .stack 4096 sablon struc _bp _cs_ip manevra dest sursa n sablon ends .data mesaj sursa_asc dest_asc

dw dw dw dw dw dw db db db db db

? 2 dup (?) ? ? ? ? cr, lf, 'Muta discul de pe turnul' ? 'pe turnul' ? 0

.code move proc far ; ; Muta n discuri de pe sursa pe dest ; folosind manevra ca zona de lucru ; push bp mov bp, sp cmp word ptr [bp].n, 1; Test caz de baza (n = 1) jne move_1 ; Salt la cazul general ; ; Mutare explicita ; mov bx, [bp].sursa ; Pregatire mesaj mov sursa_asc, bl ; Disc sursa mov bx,[bp].dest mov dest_asc,bl ; Disc destinatie puts mesaj ; Afisare mesaj pop bp retf 8 ; Revenire cu descarcare move_1: ; ; Cazul general ; mov ax, [bp].n ; Pregatire parametri dec ax ; n-1 push ax push [bp].sursa ; Sursa push [bp].manevra ; Actuala manevra devine ; destinatie push [bp].dest ; Actuala destinatie ; devine manevra call far ptr move ; Primul apel recursiv mov ax, 1 ; Pregatire parametri

33

push push push push call ; mov dec push push push push call ; pop retf move

ax [bp].sursa [bp].dest [bp].manevra far ptr move ax, [bp].n ax ax [bp].manevra [bp].dest [bp].sursa far ptr move bp 8 endp

; ; ; ; ;

n = 1 Sursa Destinatie Manevra Al doilea apel recursiv

; Pregatire parametri ; ; ; ; ; ; ; n-1 Actuala manevra devine sursa Destinatie Actuala sursa devine manevra Al treilea apel recursiv

; Re venire cu ; descarcare

; ; Program principal ; start: init_ds_es reluare: putsi <cr,lf,'Numar discuri: '> geti or ax, ax jz exit ; n = 0, stop putsi <cr,lf,'Muta '> puti ax ; Afisare n putsi <'turnuri, de pe A pe B, '> putsi <'folosind C ca'> putsi <'manevra', cr, lf> ; push ax ; n mov ax, 'A' push ax ; Sursa mov ax, 'B' push ax ; Destinatie mov ax, 'C' push ax ; Manevra call far ptr move ; Apel jmp reluare ; Reluare exit: exit_dos end start La implementarea algoritmilor recursivi, trebuie fcut o estimare a spaiului necesar de stiv. Spre exemplu, procedura move de mai sus necesit n nivele de apel recursiv. La fiecare nivel, se consum 12 octei din stiv (vezi structura ablon). Ca atare, volumul necesar de stiv este de ordinul 12*n. Dac estimm c, ntr-o aplicaie dat, valoarea lui n nu va crete peste 100, stiva acelei aplicaii trebuie s fie de cel puin 1200 de octei. nainte de a trece la implementarea unui algoritm recursiv, trebuie s ne convingem (uneori prin demonstraii teoretice complicate), c algoritmul se termin (se atinge cazul de baz) dup un numr finit de pai. Nu toate funciile definite recursiv au aceast proprietate. De exemplu, funcia f, definit pentru orice n pozitiv prin: f(n) = 5, pentru n = 7 f(n) = f(f(n+2)), pentru n diferit de 7 nu este nici mcar o funcie corect definit. ncercarea de a evalua f(5) conduce imediat la o tautologie (f(5) depinde de f(5)), iar pentru n diferit de 5 i de 7 se obine un ciclu infinit. Trebuie deci acordata atenie descrierii teoretice a algoritmului recursiv, nainte de a trece la implementare.

34

Observaiile despre implementarea algoritmilor recursivi sunt valabile i n cazul limbajelor de nivel nalt. 5.8 Proceduri cu numr variabil de parametri Dezvoltarea procedurilor cu numr variabil de parametri are c scop, pe de o parte, punerea n eviden a flexibilitii limbajului de asamblare i, pe de alt parte, ilustrarea modului de implementare a unui mecanism care exist n anumite limbaje de nivel nalt (de exemplu n C). Dezvoltarea unei proceduri cu numr variabil de parametri se bazeaz pe urmtoarele reguli: parametrii se transmit prin stiv; exista un numr (cel puin 1) de parametri fici (care nu pot lipsi) i care se transmit n vrful stivei (imediat dup adresa de revenire); unul din parametrii care sunt totdeauna prezeni conine informaii (codificate ntr-o form oarecare) despre numrul i tipul parametrilor actuali; n procedur se analizeaz parametrii fici, deducndu-se numrul i tipul parametrilor variabili, care sunt apoi extrai din stiv; parametrii variabili ca numr transmii n stiv trebuie s corespund cu descrierea lor n parametrii fici; stiva este descrcat de ctre programul apelant. Regulile de mai sus nu sunt alese ntmpltor. Astfel, faptul c parametrii fici se gsesc n vrful stivei face ca acetia s fie accesibili prin metoda standard. Programul apelant este cel care tie precis ci octei a plasat n stiv nainte de apel, deci este natural ca acesta sa descarce stiva. Se nelege acum de ce, n limbajul C, ordinea plasrii argumentelor unei proceduri n stiv este de la dreapta la stnga: n acest fel, la o procedur cu numr variabil de parametri, parametrii fici (care se afl la nceputuI listei de argumente) vor fi plasai imediat dup adresa de revenire. Imaginea stivei la intrarea n procedur este ilustrat n Figura 5.9.

Figura 5.9 Stiva la intrarea ntr-o procedur cu numr variabil de parametri S considerm un exemplu tipic de procedur cu numr variabil de parametri: o procedur printf_proc de afiare cu format. Exista un parametru fix, de tip adresa NEAR a unui ir de caractere care descrie formatul de afiare. irul de caractere poate conine: caractere obinuite, care se afieaz ca atare; secvene escape: '\n' pentru CR i LF; '\\' pentru caracterul '\'; '%%' pentru caracterul '%'; descriptori de format, compui din caracterul % i un al doilea caracter care descrie tipul afirii: %s pentru iruri de caractere; %d pentru ntregi pe 16 bii cu semn; %u pentru ntregi pe 16 bii fr semn. Pentru fiecare specificator de format, se presupune c exist un parametru variabil n stiv, care trebuie afiat corespunztor. Pentru ntregi, se transmite n stiv coninutul care trebuie afiat, iar pentru iruri, se transmite adresa de nceput, de tip NEAR. Se observ c, n toate situaiile, se transmite n stiv un cuvnt (word), ceea ce faciliteaz implementarea. Dac dimensiunile parametrilor variabili ar fi diferite, ar trebui folosit operatorul de conversie de tip PTR, pentru a ncrca parametrul n mod corect.

35

Pentru afiarea propriu-zis se vor folosi macroinstruciunile de apel puts (iruri), puti (ntregi cu semn), putu (ntregi fr semn) i putc (afiare caracter), descrise n Capitolul 2. Procedura i toate adresele implicate se consider de tip NEAR. n Figura 5.10 este descris coninutul stivei la un apel tipic al acestei proceduri.

Figura 5.10 Coninutul stivei la intrarea n printf_proc Pentru accesul la stiva, se va utiliza ablonul: sablon struc _bp _ip format par sablon ends dw dw dw dw ? ? ? ?

; Adresa sir format ; Primul parametru

Procedura va consta dintr-o bucl de analiz a caracterelor din irul format, a crui adres e gestionat n BX. Astfel, [BX] va reprezenta caracterul curent. Pentru specificatorii de format, se gestioneaz un contor de parametri variabili, n registrul DI, care va lua valorile 0, 2, 4 etc. (toi parametrii variabili sunt pe 16 bii). Accesul la parametrii variabili se va face prin expresia [BP][DI].PAR, deci printr-o adresare bazat i indexat, cu deplasament. Se trateaz corespunztor secvenele escape (care ncep cu \) i specificatorii de format (care ncep cu %), ambele situaii presupunnd citirea unui caracter suplimentar din irul care descrie formatul. Dac parametrii nu ar avea toi aceeai dimensiune, s-ar utiliza expresii de genul DWORD PTR [BP][DI].PAR, caz n care registrul DI ar fi actualizat cu dimensiunea parametrului (n cazul de fa, cu 4). Implementarea este urmtoarea: printf_proc proc near push bp mov bp, sp mov bx, [bp].format mov di, 0 pr_loop: mov test jnz jmp aici1: cmp jne inc mov cmp je cmp je cap je cap je jmp al, [bx] al, al aici1 pr_end al, '%' pr_char bx al, [bx] al, 's' pr_str al, 'd' pr_int al, 'u' pr_u al, '%' aici2 pr_err ; Salvare BP ; Adresa sir format ; Contor parametri ; variabili ; Caracter curent ; Test sfarsit de sir ; care descrie formatul ; ; ; ; ; ; ; ; ; ; ; ; ; Test spec. format Nu este spec. Daca da, se citeste caracterul urmator Este %s ? Da, salt la afisare Este %d ? Da, salt la afisare Este %u ? Da, salt la afisare Este %% ? Nu a fost nici unul

36

; din specificatorii ; asteptati: se afiseaza ; un mesaj de eroare aici2: putc inc jmp pr_char: cmp je putc inc jmp pr_esc: inc mov cmp jne putc putc inc jmp esc1: cmp jne putc inc jmp al bx pr_loop al, '\' pr_esc al bx pr_loop bx al, [bx] al, 'n' esc1 cr lf bx pr_loop al, '\' pr_err al bx pr_loop ; Afisare caracter ; Avans adresa sir ; Reluare ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Este secventa escape ? Da, salt la tratare Daca nu, e vorba de un car. obisnuit, care se afiseaza si se reia bucla de parcurgere Tratare secventa escape Luam urmatorul caracter Este \n ? Nu, salt Da, se afiseaza CR LF si se reia bucla de parcurgere Este \\ ? Nu, secventa escape gresita Da, afiseaza \ si reia bucla de prelucrare

; ; Afisarea parametrilor variabili ; pr_str; push mov puts pop pr_next: inc add jmp pr_int: puti jmp pr_u: putu jmp pr_err: bx bx, [bp+di].par [bx] bx bx di, 2 pr_loop [bp+di].par pr_next [bp+di].par pr_next ; ; ; ; Afisare sir. Preluam adresa din stiva si afisam sirul (cu salvare/restaurare BX

; Car. urmator in format ; Contor par. variabili ; Reluare ; intreg cu semn ; Reluare ; intreg fara semn ; Reluare

; Mesaj de eroare putsi <cr, lf, 'printf: Eroare format', cr, lf> pr_end: pop bp ; Refacere BP ret ; Re venire printf_proc endp Ne propunem acum s dezvoltm o macroinstruciune de apel, numit printf, care s se apropie ct mai mult de un limbaj de nivel nalt, deci s putem scrie n textul surs ceva de genul: printf <"Numerele m n si p sunt: %d %d\n">, <m, n, p>

37

Macroinstruciunea va avea, deci, un prim parametru (irul care descrie formatul) i o list de parametri variabili (care poate chiar lipsi): printf macro format, param Scrierea unei astfel de macroinstruciuni ridic probleme deosebite, dar pe care operatorii i directivele limbajului de asamblare Ie pot rezolva. irul care descrie formatul se definete n segmentul de date, printr-o tehnic similar celei de la macroinstruciunea putsi, descris n Capitolul 5. Problema cea mai dificil este c parametrii variabili trebuie pui n stiv n ordine invers dect apar n lista din apelul macroinstruciunii. Lista poate fi parcurs cu instruciunea IRP, dar numai de la stnga la dreapta. Pentru a pune parametrii n ordinea dorit, se va parcurge lista lor de doua ori. La prima parcurgere, se contorizeaz numrul de parametri (cu operatorul de atribuire =) i se creeaz spaiu n stiv, prin secvena: N = 0 irp x, <param> sub sp, 2 N = M + 1 endm La a doua parcurgere, se plaseaz parametrii n stiv. Problema este c indicatorul SP trebuie s fie incrementat cu 2, deci nu se poate utiliza o simpl instruciune PUSH. Se executa deci secvena: irp x, <param> add sp, 2 push x add sp, 2

endm care pune parametrii x n stiv, de sus n jos (vezi Figura 5.11).

Figura 5.11 Plasarea parametrilor n stiv la macroinstruciunea printf Toata aceasta secvena se execut numai dac lista param este nevid. Dup depunerea parametrilor variabili, se decrementeaz SP cu 2*N (deci se aduce SP pe primul parametru), se depune n stiv adresa formatului i se apeleaz procedura printf_proc. Contorizarea parametrilor n constanta simbolic N asigur i o descrcare comod a stivei, prin adunarea valorii 2*(N+1). Dac irul format lipsete, se foreaz o eroare de asamblare. Implementarea complet a macroinstruciunii este urmtoarea: printf macro format, param local loc_for ifb <format> %out : Lipsa format.... .err .exitm endif

38

.data loc_for

db db

format 0

;; Definire sir format ;; Contor par. Variabili ;; Daca exista par. var. ;; Creare spatiu ;; Contor

.code N = 0 ifnb <param> irp x, <param> sub sp, 2 N = N + 1 endm irp x, <param> add sp, 2 push x add sp,2 sp, 2*M ax, loc_for ax printf_proc sp, 2*(N+1)

;; in jos ;; in sus ;; in jos ;; SP pe primul par. var. ;; ;; ;; ;; Adresa format Depunere in stiva Apel Descarcare stiva

endm sub endif lea push call add endm

S considerm un exemplu de apel, n care avem definite datele: .data n1 n2 n3 sir1 sir2 sir3 s1 s2 s3 dw dw dw db db db dw dw dw 1234 -3 4321 "abcdef",0 "1234567890",0 "Acesta e un sir...",0 sir1 sir2 sir3

Secvena de test (programul principal) este urmtoarea: .code start: init_ds_es printf <"Numarul 1: %d\n">, <n1> printf <"Numerele 2 si 3: %d %d">, <n2,n3> printf <"\nSirul 1: %s %% \\ %%>, <s1> printf <"\nSirul 2: %s\nSirul 3: %s">, <s2,s3> printf <"\nDoar format (fara descriptori) \n"> printf <"n2 cu semn = %d, n2 fara semn = %u\n">, <n2,n2> exit_dos end start Se observ necesitatea definirii unor variabile pointer (s1, s2, s3) care conin adresele irurilor de caractere sir1, sir2 si sir3. Rularea acestui exemplu produce ieirea la consol: Numarul 1: 1234 Numerele 2 si 3: -3 4321 Sirul 1; abcdef % \ % Sirul 2: 1234567890 Sirul 3: Acesta e un sir... Doar format (fara descriptori) n2 cu semn = -3, n2 fara semn = 65533

39

Mai trebuie observat c tehnica de transfer a parametrilor variabili, expus mai sus, asigur i o bun protecie a controlului programului n situaii de eroare, adic atunci cnd descriptorii de format nu coincid cu parametrii, att ca numr, ct i ca tip. S consideram, de exemplu, apelurile: printf <"Ceva gresit %d %d %d\n">, <n1, n2> printf <"Ceva si mai gresit %d %d\n">, <n1, s2, s3> n primul caz, formatul precizeaz c n stiv sunt trei parametri de tip ntreg, dar la apel nu se transmit dect doi. Procedura va afia un al treilea parametru fictiv, dar, deoarece stiva este descrcat de programul apelant, revenirea se face corect i programul nu se distruge. n al doilea caz, primul parametru se afieaz corect, al doilea (o adresa de ir) este afiat incorect ca ntreg, iar al treilea este pur i simplu ignorat. i n acest caz, revenirea n programul apelant i continuarea sa nu sunt compromise. 5.9 Tehnici avansate de programare n subcapitolele anterioare au fost descrise elementele de baz ale programrii n ASM (aciunile principale din programarea structurat, proceduri, definiii de date etc.). Cu aceste elemente de baz, se pot aplica orice tehnici de programare cunoscute de la limbaje de nivel nalt. Vom exemplifica o asemenea tehnic, anume cea a automatelor de stare. Considerm problema elementar a numrrii cuvintelor dintr-un ir de caractere. Se consider o serie de caractere speciale, numite delimitatori (n cazul de fa: spaiu, punct, virgula, semn de ntrebare, semn de exclamare i cratim). Evident, se pot specifica orice fel de delimitatori. Incrementarea numrului de cuvinte nu depinde numai de caracterul curent din ir. De exemplu, un caracter care nu este delimitator va conduce la incrementarea numrului de cuvinte numai n cazul n care caracterul anterior a fost un delimitator. Acest tip de decizie, care se bazeaz pe o informaie anterioar, sugereaz utilizarea unei variabile de stare, care s memoreze starea curent a prelucrrii. n cazul de fa, vom avea doua stri: n interiorul unui cuvnt i n exteriorul unui cuvnt. Notm cele dou stri cu IN_W i OUT_W. Pentru memorarea lor, este suficient o variabil de stare binar. Algoritmul se modeleaz ca un automat, care accept intrri i genereaz ieiri. Funcie de intrri i de starea curent, se trece ntr-o stare urmtoare, emind sau nu o ieire. n cazul de fa, intrrile sunt: in_1 = caracterul curent din ir nu este delimitator; in_2 = caracterul curent din ir este delimitator. Ieirea automatului este: out = incrementeaz numrul de cuvinte Trebuie acum s codificm strile, intrrile i ieirile. Pentru simplitate, vom utiliza registrele procesorului: stare = IN_W: CX = 1 stare = OUT_W: CX = 0 in_1: BX = 1 in_2: BX = 0 out: Incrementeaz AX. Descriem acum funcionarea automatului, prin diagrama din Figura 5.12. Dac suntem n interiorul unui cuvnt (starea IN_W), atunci un delimitator (intrarea in_2) provoac trecerea n exteriorul unui cuvnt (starea OUT_W), iar un caracter care nu este delimitator (intrarea in_1) menine starea IN_W. Dac suntem n exteriorul unui cuvnt (starea OUT_W), atunci un delimitator (intrarea in_2) menine starea OUT_W, iar un caracter care nu este delimitator (intrarea in_1) provoac trecerea n starea IN_W i forarea semnalului de ieire out. Iniial, automatul se afl n starea OUT_W. Se remarc faptul c, n codificarea aleas, starea urmtoare este dictat de codificarea intrrii (mai precis. comutarea strii va nsemna o simpla instruciune MOV CX, BX).

Figura 5.12 Descrierea funcionrii unui automat de stare Evoluia variabilei de stare, a intrrilor in_1 i in_2, precum i a ieirii out n cazul prelucrrii irului de caractere Un text oarecare." sunt ilustrate n Figura 5.13. Implementarea algoritmului urmrete ndeaproape funcionarea automatului i const n principal din: evaluarea intrrilor;

40

comutarea strii; forarea ieirii.

Figura 5.13 Evoluia automatului de stare n cazul irului ,,Un text oarecare. Presupunem c adresa irului este transmis procedurii n DS:SI i c rezultatul este ntors n AX. Implementarea este urmtoarea: count push push push mov mov reluare: cmp je cmp je cmp je cmp je cmp je cmp je cmp je cmp je proc near cx bx si cx, 0 ax, cx byte ptr [si], 0 gata byte ptr [si], ' ' et_1 byte et_1 byte et_1 byte et_1 byte et_1 byte et_1 byte et_1 ptr [si], tab ptr [si], '.' ptr [si], ',' ptr [si], '?' ptr [si], '!' ptr [si], '-' ; Salvari ; registre ; Stare initiala ; = OUT_W ; Numar de cuvinte = 0 ; Test sfarsit de sir ; Evaluare intrari: ; Blank ; Tab ; Punct ; Virgula ; Semn de intrebare ; Semn de exclamare ; Cratima

mov bx, 1 ; Nu e delimitator jmp et_2 et_1: mov bx, 0 ; Delimitator ; ; in acest moment, intrarile sunt pozitionate ; BX = 1 (in_1) sau BX = 0 (in_2) et_2: cmp cx, 0 ; Stare = OUT_W ? je et_3 mov cx, bx ; Stare = intrare jmp et_5 et_3: cmp bx, 1 ; In starea OUT_W si jne et_4 ; cu intrarea in_1,

41

inc

ax

; ; ; ;

se genereaza iesirea out (incrementeaza numarul de cuvinte)

et_4: mov et_5: inc jmp gata: pop pop pop ret count

cx, bx si reluare si bx cx endp

; Stare = intrare ; Trecere la ; caracterul urmator ; Refaceri ; registre ; Rezultat in AX

Un program de test al procedurii count este listat mai jos. Se citesc de la consol iruri de caractere (pn la introducerea irului vid), se apeleaz procedura count i se afieaz numrul de cuvinte din irul introdus. .model small include io.h .data sir db 80 dup (0) .stack 1024 .code

; Spatiu de lucru

start: init_ds_es iar: putsi <'Introduceti un sir: '> gets sir ; Citire sir mov al, sir ; Test sir test al, al ; vid jz exit putsi <'Sirul are '> lea si, sir ; Apel procedura call count ; Nr. cuvinte in AX puti ax ; Afisare putsi <' cuvinte', cr, lf> jmp iar ; Reluare exit: exit_dos end start 5.10 Programarea coprocesoarelor matematice n Capitolul 2, a fost prezentat setul de instruciuni al coprocesoarelor matematice 80x87. Coprocesorul recunoate variabile ntregi i toate cele trei tipuri de numere reale (precizie simpl, dubl i extins). n acest subcapitol, ne propunem s dezvoltm un set de proceduri i macroinstruciuni care s rezolve problema conversiei din formatul real (n simpl precizie), n format ASCII (ir de caractere), ceea ce va permite apoi i introducerea i afiarea de numere reale la consol. 5.10.1 Conversia ASCll-real Dezvoltm o procedur de tip far, cu numele ATOF_PROC, care primete n stiv o adres near a unui ir de caractere i o adres near a unui numr real. ablonul de acces la stiva este: sablon_atof struc adr_num adr_sir sablon_atof ends dw dd dw dw ? ? ? ? ; Loc pentru BP ; Loc pentru adresa de revenire

42

Procedura de conversie se bazeaz pe urmtorul algoritm: valoare = 0; divizor = 1.0 punct = FALSE; minus = FALSE; preia caracter din sirul sursa; if (caracter == '-' ) { minus = TRUE; preia urmatorul caracter din sirul sursa; } while (caracterul curent este cifra zecimala sau '.') { if (caracter == '.') punct = TRUE; else { cifra = caracter - '0'; cifra_float = (float) cifra; valoare = 10.0 * valoare + digit_float; if (punct) divizor = divizor * 10.0; } preia urmatorul caracter din sir; } if (punct) valoare = valoare/divizor; if (minus) valoare = - valoare; return valoare; Ideea general este calculul numrului ca i cnd nu ar avea punctul zecimal i mprirea apoi la o putere a lui 10, calculate dup numrul de zecimale. De exemplu, irul 123.456 va conduce la calculul valorii 123456, care se mparte apoi la 1000. Implementarea este urmtoarea: .data _zece dd 10.0 _punct db ? _minus db ? _cifra dw ? .code atof_proc proc far push bp mov bp, sp push bx ; Salvari push si ; registre fld1 ; ST <- 1.0 fldz ; ST <- 0.0, ST(1) <- 1.0 ; ; Asignam valoare in ST si divizor in ST(1) ; mov _punct, 0 mov _minus, 0 xor bh, bh ; Necesar la depunere intreg mov si, [bp].adr sir ; Adresa sir cmp byte ptr [si], '-' ; Test '-' jne atof_1 mov _minus, 1 inc si atof_1:

43

mov cmp jne mov jmp atof_2: cmp jb cmp ja sub mov fmul fiadd cmp jne fxch fmul fxch

bl, [si] bl, '.' atof_2 _punct, 1 atof_3 bl, '0' atof_4 bl, '9' atof_4 bl, '0' _cifra, bx _zece _cifra _punct, 1 atof_3 _zece

; Preia caracter ; Test '.' ; Salt la reluare ; Test ; cifra ; zecimala ; ; ; ; ; ; ; ; ; ; ; ; Conversie la intreg Memoram ca intreg valoare = 10*valoare valoare = valoare + cifra Test punct Schimbam ST cu ST(1) pentru ca vrem sa inmultim divizorul cu zece, apoi schimbam la loc

atof_3: inc si jmp atof_1 atof_4: fdivr cmp _minus, 1 jne atof_5 f_comp _zero je atof_5 fchs atof_5: mov bx, [bp].adr_num fstp dword ptr [bx] fwait pop si pop bx pop bp retf atof_proc endp

; Reluare ; bucla ; ; ; ; impartire ST la ST(1) cu descarcarea stivei Rezultat in ST Test semn

; Daca e 0.0 nu ; schimba semnul ; Schimbare semn ; ; ; ; Adresa numar real Depunere ST cu descarcarea stivei, apoi FWAIT

; Refaceri ; registre ; Revenire

Se observ c stiva coprocesorului este lsat n starea n care era la intrarea in procedur, lucru care trebuie avut n vedere la toate programele care lucreaz cu 80x87. ntr-o implementare mai pretenioas, s-ar salva toate cele 8 registre ale coprocesorului ca variabile de tip TBytes, ntr-o zon proprie de memorie i s-ar reface la revenirea n programul apelant. De asemenea, bucla de citire caractere ar trebui s testeze dac s-a ntlnit deja punctul zecimal. Pentru simplitate, s-a presupus aprioric c irul surs conine cel mult un punct zecimal. Scriem acum o macroinstruciune de apel, cu numele atof, care are ca parametri irul de caractere surs i variabila real n care se va depune rezultatul. Ambii parametri sunt folosii n instruciuni LEA, care preiau adresele near i Ie transmit ctre procedura atof_proc. atof macro push lea push lea push a_sir, a_num si si, a_sir si si, a num si

44

call add pop endm

far ptr atof_proc sp, 4 si

Dispunnd de macroinstruciunea atof, putem scrie o macroinstruciune cu numele getf, care s citeasc un numr real de la consol. Operaia se compune dintr-o citire de ir de caractere i o conversie ASCII-real. Parametrul macroinstruciunii este variabila dword n care se depune numrul real citit. getf macro a_num local sir .data sir db 80 dup (0) .code gets sir atof sir, a_num endm 5.10.2 Compararea numerelor reale O problem important care se pune este compararea a dou numere reale. Procesorul dispune de instruciuni de comparare, n urma crora se poziioneaz o serie de flaguri interne din cuvntul de stare (Status Word). Acest cuvnt poate fi depus n memorie cu instruciuni FSTSW i citit apoi prin instruciuni 80x86. Preferm totui o alt abordare, mai simpl, anume efectuarea diferenei celor doi operanzi i poziionarea corespunztoare a bistabililor ZF i CF din 80x86. Astfel, se vor putea utiliza instruciuni de salt condiionat specifice 80x86 (JE, JB, JGE etc.). Testarea semnului diferenei se face prin examinarea fizic a bitului de semn din reprezentarea intern a numerelor n simpl precizie. Testarea cazului de egalitate se face testnd dac toi cei patru octei ai diferenei sunt nuli, ignornd bitul de semn. Dezvoltm macroinstruciunea F_COMP, cu un operand, care compar numrul real din vrful stivei coprocesorului cu operandul specificat. f_comp macro local .data temp dd .code push ax fld st fsub fstp mov and jnz mov or jz etgt: mov cmp jmp etlt: mov or and jz mov cmp jmp eteq: val temp, etgt, etit, eteq, gafca ? ; Spatiu de lucru ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Salvare AX Se face o copie a varfului stivei, pentru. a nu altera ST initial ST <- ST - val Depunem diferenta Luam ultimul octet din reprezentare Filtru bit de semn Diferenta negativa ? Nu, testam daca nu este zero Zero real are toti cei 4 octeti null Este zero ?

dword ptr val dword ptr temp al, byte ptr temp+3 al, 10000000B etlt ax, word ptr temp ax, word ptr temp+2 eteq ax, 2 ax, 1 gata ax, word ptr temp ax, word ptr temp+2 ax, 7FFFH eteq ax, 1 ax, 2 gata

; Nu, inseamna ca e mai mare ; Facem o comparatie pentru a ; pozitiona indicatorii

; -0 si 0 sunt identice ; Cazul mai mic

45

mov cmp gata: pop endm

ax, 1 ax, 1 ax

; Cazul egal ; Refacere AX si gata

5.10.3 Conversia real-ASCII Dezvoltm acum procedura ftoa_proc, care primete n stiv adresa near a unui numr real i adresa near a unui ir de caractere. Procedura convertete numrul real ntr-un ir de caractere care conine reprezentarea valorii reale n format tiinific: x.xxxxxxEyy Accesul la parametrii din stiv se face prin structura ablon: sablon_ftoa struc val siradr sablon_ftoa end dw dd dd dw ? ? ? ?

Implementarea folosete o serie de constante i variabile de lucru, definite mai jos. Algoritmul de conversie este urmtorul: ; ; val = Valoarea reala care se converteste ; sir = Adresa sirului destinatie ; if (val < 0.0) { *s++ = '-'; val = -val; } _exp = 0; if (val != 0.0) { if (val >= 0.0) { do { val = val / 10.0; _exp = _exp +1; } while (value >= 10.0) } else { while (val < 1.0) { val = val * 10.0; _exp = _exp - 1; } } val = val + 0.0000005; if (value > 10.0) { value = value / 10.0; _exp = _exp + 1; } } _cifra = (int) val; /* Obligatoriu cu trunchiere, nu cu rotunjire */ *s ++ = _cifra + '0'; *s++ = '.';

46

for (i = 1; i <= 6; i++) { val = 10.0 * (val - (float) _cifra) _cifra = (int) val; /* Obligatoriu cu trunchiere, nu cu rotunjire */ *s++ = _cifra + '0'; } *s++ = 'E'; if(_exp < 0) { *s++ = '+'; _exp = - _exp; } else *s++ = '+'; /* scrie _exp ca doua cifre in sir */ *s = 0; /* Terminator de sir */ Se aduce numrul n intervalul [1.0, 10.0), prin mpriri sau nmuliri succesive cu 10. nmulirile sau mpririle cu 10 sunt contorizate n variabila ntreag _exp (exponent zecimal). n acest moment, partea ntreag a numrului are o singur cifr, care se poate extrage i depune n ir. Se determin partea fracionar, prin scderea prii ntregi, se nmulete cu 10 i se reia generarea cifrelor. Acest proces se repeta de 5 ori, pentru cifrele de dup virgul. n prealabil, se realizeaz o rotunjire a numrului, prin adunarea valorii reale 0.000005. Algoritmul depinde esenial de modul n care se face conversia real-ntreg. Este obligatoriu ca aceast conversie s se realizeze prin trunchiere, nu prin rotunjire. De exemplu, valoarea 2.999 trebuie s genereze prima cifr 2, i nu 3. Coprocesorul poate opera n mai multe moduri de rotunjire / trunchiere. Aceste moduri sunt controlate de biii 10 i 11 (cmpul RC sau Round Control), din cuvntul de control (Control Word). Pentru situaia de fa, este adecvat modul RC = 1. Procedura trebuie deci s poziioneze cmpul RC din cuvntul de control, printr-o salvare / ncrcare n memorie. La revenirea n programul apelant, se va reface cmpul RC original. Procedura necesit o serie de constante reale i variabile de manevr, definite mai jos. Numele variabilelor coincid cu cele de la descrierea algoritmului. Evident, vom aloca variabila reala n vrful stivei 80x87. ExponentuI este gestionat ca numr ntreg. .data _zece _unu _zero _round _exp _cifra _temp _cw dd dd dd dd dw dw dd dw 10.0 1.0 0.0 0.0000005 ? ? ? ?

Implementarea propriu-zis este urmtoarea: .code ftoa_proc proc far push bp mov bp, sp push push push push fstcw mov and or mov fldcw ax bx si cx _cw ax, _cw ax, NOT 0000110000000000B ax, 0000010000000000B _cw, ax _cw ; Salvare ; registre

; Salvare Control Word ; ; ; ; Filtru bitii 10 si 11 Fortare RC = 1 inapoi in memorie inapoi in 80x87

47

mov mov fld f_comp jg mov inc fchs ftoa_poz: f_comp jne jmp ftoa_aici: f_comp jnge ftoa_3: fdiv add

_exp, 0 si, [bp].siradr dword ptr [bp].val _zero ftoa_poz byte ptr [si], '-' si _zero ftoa_aici ftoa_1 _zece ftoa_2 _zece exp, 1

; ; ; ; ; ; ; ;

Exponent Adresa sir de caractere Valoare reala in ST Este > 0 ? Nu, depunem '-' in sir si schimbam semnul numarului real

; Este diferit de 0.0 ? ; Da, il convertim ; Este < 10 ? ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Nu, il impartim la 10 si tinem minte la exponent pana cand ajunge mai mic strict decat 10 Este mai mare sau egal ca 1 ? Da, salt Nu, il inmultim cu 10 si tinem minte la exponent, pana ajunge mai mare sau egal ca 1 Rotunjim la 7 zecimale Poate a depasit acum 10 ?

f_comp _zece jnl ftoa_3 ftoa_2: f_comp _unu jge fmul sub jmp ftoa_1: fadd ftoa_1 _zece _exp, 1 ftoa_2 _round

f_comp _zece jna fdiv add ftoa_4: fist mov add mov inc mov inc mov ftoa_5: fisub fmul fist mov add ftoa_4 _zece _exp, 1 _cifra bl, byte ptr _cifra bl, '0' [si], bl si byte ptr [si], '.' si cx, 6 _cifra _zece _cifra bl, byte ptr _cifra bl, '0'

; Daca da, corectam ; ; ; ; ; ; ; Generam partea intreaga Luam cifra propriu-zisa, o convertim la caracter ASCII si o depunem in sir

; Apoi punctul zecimal ; Bucla de 6 cifre ; dupa punct ; ; ; ; ; ST = partea fractionara ST = ST * 10 Partea intreaga Determinare cifra

48

mov inc loop fstp mov inc cmp jge mov neg jmp ftoa_6: mov ftoa_7: inc mov aam or mov inc mov inc mov pop pop pop pop pop retf ftoa_proc

[si], bl si ftoa_5 _temp byte ptr [si], 'E' si _exp, 0 ftoa_6 byte ptr [si], '-' _exp ftoa_7 byte ptr [si], '+' si ax, _exp ax, 3030H [si], ah si [si], al si byte ptr [si], 0 cx si bx ax bp ; Revenire endp ; Descarcam stiva 8087 ; Notatie stiintifica ; Test semn exponent ; Negativ ; Schimbam semnul ; Pozitiv ; ; ; ; ; ; Exponent AH = _exp / 10; AL = _exp MOD 10 Ambele cifre convertite la ASCII Depunere in sir

; ; ; ;

in fine, terminatorul de sir Refacere registre

Se observ utilitatea foarte mare a macroinstruciunii f_comp, care face ca implementarea algoritmului s fie o simpl transpunere a descrierii de tip pseudo-cod. Dezvoltm acum o macroinstruciune de apel cu numele ftoa i cu parametri adecvai (valoarea real i irul destinaie): ftoa macro push lea push mov push mov push call add pop endm val, sir ax ax, ax ax, ax ax, ax far sp, ax sir word ptr val + 2 word ptr val ptr ftoa_proc 6

Dispunnd de macroinstruciunea ftoa, putem dezvolta o macroinstruciune putf, care s afieze la consol un numr real, prin conversie real-ASCII i afiare ir: putf macro val local .data sir . code sir db 30 dup (0)

49

ftoa val, sir puts sir endm 5.10.4 Un program de test Dac presupunem macroinstruciunile de mai sus definite ntr-un fiier float.h, putem scrie un mic program de test, care citete un numr real de la consol i afieaz valorile incrementate de 10 ori. .model large .8087 include io.h include float.h .stack 1024 .data numar dd ? unu dd 1.0 .code start: init_ds_es finit putsi <'Introduceti un numar real: '> getf numar putsi <'Valorile incrementate sunt', cr, lf> mov cx, 10 iar: putf numar putsi <cr, lf> fld numar fadd unu fstp numar loop iar exit_dos end start Se observ instruciunea FINIT, care iniializeaz coprocesorul matematic. Directiva .8087 instruiete asamblorul s accepte mnemonice specifice coprocesorului 8087. Funcie de tipul coprocesorului i al procesorului de baz, se pot utiliza directivele .287, .387 sau .486. Procedurile de mai sus opereaz cu numere n simpl precizie. Trecerea la precizie dubl i extins se face ns foarte uor. Trebuie doar ca variabilele externe i constantele reale utilizate s fie declarate de tip qword (8 octei), respectiv tbytes (10 octei), deoarece formatul intern al procesorului este cel de precizie maxim. Dei procedurile care implic operaii cu numere reale par complicate, implementrile din acest subcapitol arat c o abordare sistematic i disciplinat permite dezvoltarea unui cod surs clar, uor de urmrit i de ntreinut.

50