Sunteți pe pagina 1din 4

Limbaje 

de asamblare – laborator 6 

Instrucțiuni de prelucrare șiruri 

Procesoarele x86 au instucțiuni specializate de prelucrare a unor șiruri de valori din


memorie. Aceste instrucțiuni folosesc regiștri index (SI, DI) în combinație cu regiștri segment
(DS cu SI și respectiv ES cu DI), acumulatorul care poate fi AL, AX sau EAX după cum
instrucțiunea e pe 1, 2 sau 4 octeți și incrementează/decrementează regiștrii index folosiți cu 1,
2 sau 4 în funcție de indicatorul de condiție DF (0 = incrementare, 1 = decrementare).
Variantele pe 32 biți există doar pe 386+ și folosesc ESI și EDI în loc de SI, DI.
Mnemonicele sunt formate din 3 litere care descriu operația, litera S și eventual una din
B, W sau D (bytes, words, dwords). Dacă ultima lipsește atunci se poate folosi un operand însă
acesta va fi ignorat, doar dimensiunea lui contează (1, 2 sau 4 octeți). Operațiile posibile sunt:
- MOVS = move, copiază DS:[SI] la ES:[DI]
- LODS = load, copiază DS:[SI] în acumulator
- STOS = store, copiază acumulator în ES:[DI]
- SCAS = scan, compară acumulatorul cu ES:[DI] și setează flagurile
- CMPS = compare, compară DS:[SI] cu ES:[DI] și setează flagurile
Comparațiile din SCAS și CMPS sunt asemănătoare cu ce face instrucțiunea CMP,
adică se face o scădere dar rezultatul nu este stocat nicăieri însă flagurile corespunzătoare sunt
modificate.
Flag-ul DF poate fi modificat cu instrucțiunile CLD (clear direction, resetează DF)
pentru direcție normală, de la adrese mici la mari, și STD (set direction, setează DF) pentru
direcție inversă.
Se observă ușor că aceste operații nu prelucrează decât un element al unui șir, nu tot
șirul. Pentru aceasta este necesar să le combinăm cu instrucțiuni de ciclare, cel mai adesea din
familia LOOP sau prefixe de ciclare din familia REP.
Instrucțiunea LOOP are ca operand o etichetă unde va executa un salt scurt
(deplasament între –128 și 127 octeți) dacă după decrementarea registrului CX fără a modifica
flagurile acest registru este zero. În condițiile în care saltul este înapoi (la o instrucțiune
anterioară) și nimic altceva nu modifică CX rezultă o buclă executată de CX ori, dar cel puțin
o singură dată fiindcă testul este la sfârșit. În caz că CX inițial este zero bucla se va executa de
65536 ori.
LOOPE (loop while equals) și sinonima LOOPZ (loop while zero) adaugă o condiție
suplimentară la CX!=0, și anume ZF=1. Se folosește de obicei împreună cu CMPS pentru a
opri bucla la prima diferență detectată.
LOOPNE (loop while not equals) și sinonima LOOPNZ (loop while not zero) adaugă
condiția ZF=0 și se folosește de obicei împreună cu SCAS pentru a opri bucla la prima găsire
a acumulatorului în șir.
În cazul în care nu este nevoie de mai mult de o singură instrucțiune într-o buclă se
preferă folosirea prefixelor REP în fața instrucțiunii de executat. Aceasta elimină necesitatea
unei etichete și sunt mai rapide. Există REP echivalent cu LOOP peste o singură instrucțiune
și mai există REPE/REPZ și REPNE/REPNZ echivalente cu cele descrise anterior. De exemplu
în loc de:
et:
movsw
loop et
putem scrie doar:
rep movsw
OBS: MOVS se folosește de obicei pentru a copia o zonă de memorie de lungime CX
în alta, însă efectul poate fi diferit dacă zonele de memorie se suprapun. Un exemplu:
.data
sir db 'bla123456'
.code
mov ax, @data
mov ds, ax
mov es, ax
mov si, offset sir
mov di, si
add di, 3
mov cx, 6
cld
rep movsb
Date fiind valorile SI, DI, CX de aici intenția ar fi fost de a copia 6 octeți, anume 'bla123' cu
3 poziții la dreapta, obținând astfel 'blabla123' dar de fapt se obține 'blablabla'. Aceasta
deoarece movsb copiază ce găsește la momentul apelării în DS:[SI] nu ce era inițial acolo. Șirul
devine pe rând:
bla123456
blab23456
blabl3456
blabla456
blablab56
blablabl6
blablabla
unde am sublinat octetul care urmează să fie copiat la fiecare pas. Efectul acesta poate fi folosit
pentru a copia repetat o structură de date de lungime arbitrară. Dacă însă vrem să obținem ce
am presupus la început va trebui să copiem invers, de la ultimul octet la primul:
bla123456
bla123453
bla123423
bla123123
bla12a123
bla1la123
blabla123
Omițând "introducerea" asta ar arăta astfel:
mov si, offset sir
add si, 6
mov di, si
add di, 3
mov cx, 6
std
rep movsb

Alte exemple 

Funcția strcpy din C/C++ copiază un șir terminat cu zero în altul, presupunând că există
destul spațiu la destinație pentru a cuprinde șirul inițial cu tot cu terminator. Presupunem că
DS:SI și ES:DI sunt adresele sursă și destinație, atunci:
strcpy PROC
cld ; DF = 0
@@et1:
lodsb ; DS:[SI] -> AL
test al, al ; ZF=0 daca AL=0
jz @@et2
stosb ; AL -> ES:[DI]
jmp @@et1
@@et2:
stosb ; copiem si terminatorul
ret
strcpy ENDP
Directiva LOCALS @@ trebuie să fie înainte de procedură, de obicei este prima linie din
program. Ca observație, atunci când încărcarea unui registru sau o comparație se pot face
evitând o instrucțiune cu operand imediat este preferabil să se facă asta pentru că rezultatul este
mai rapid și codul mai mic. De exemplu pentru a compara AL cu 0 am folosit test al, al
nu cmp al, 0 pentru că prima nu are operand imediat ci doar regiștri.

Funcția strlen din C/C++ dă lungimea unui șir de octeți terminat cu zero, excluzând
terminatorul. Rezultatul îl vom da în AX, la fel ca în C:
strlen PROC
xor cx, cx ; CX = 0
xor al, al ; AL = 0
cld ; DF = 0
repne scasb ; repeta pana ES:[DI]=0 sau CX=0
mov ax, 0FFFFh ; AX=65535
sub ax, cx ; AX=65535-CX
ret
strlen ENDP
Valoarea 65536–CX este lungimea șirului ca urmare a repne scasb incluzând
terminatorul, asta presupunând că s-a găsit un zero în zona de memorie scanată altfel rezultatul
este irelevant. Deci lungimea șirului este 65535–CX. Aceeași observație ca la exemplul
precedent: am folosit xor cx, cx și xor al, al pentru a încărca acești regiștri cu zero. Dar
când a fost vorba de a pune 65535 în AX atunci am preferat varianta cu operand imediat, asta
pentru că nu există o singură instrucțiune care să pună 65535 într-un registru. Se putea folosi
xor ax, ax urmat de dec ax, însă asta înseamnă două instrucțiuni.

Compararea a două șiruri în C/C++ se face cu funcția strcmp. Aceasta are ca parametri
adresele de început a două șiruri terminate cu zero, sir1 și sir2, și returnează o valoare negativă
dacă sir1<sir2, zero dacă sunt egale și pozitivă dacă sir1>sir2. Noi preferăm însă o variantă mai
puțin portabilă, însă mai rapidă fiindcă poate fi folosită direct cu instrucțiuni de salt condiționat
gen JB, JE, JA, JBE, JAE: ZF=1 dacă sunt egale, altfel ZF=0 și CF=1 dacă sir1<sir2 respectiv
CF=0 dacă sir1>sir2. Ca și cum am compara două numere fără semn cu CMP.
strcmp PROC
xor cx, cx ; CX = 0
cld ; DF = 0
@@et0:
cmp byte ptr ds:[si], 0
jne @@et1
cmp byte ptr es:[di], 0
je @@exit ; ambele siruri se termina si sunt egale
;; sir1<sir2, sir1 se termina
stc ; CF=1, avem ZF=0 de la JE
ret
@@et1:
cmp byte ptr es:[di], 0
jne @@et2
;; sir1>sir2, sir2 se termina
xor al, al ; AL=0, ZF=1, CF=0
inc al ; AL=1, ZF=0, CF=0
ret
@@et2:
cmpsb
loope @@et0
@@exit:
ret
strcmp ENDP
Observați că problema asta este un pic mai complicată, asta deoarece nu știm de la
început care șir este mai lung și când să terminăm algoritmul. Se observă și că avem mai multe
puncte de ieșire din procedură cu ret. Este mai avantajos decât ar fi jmp @@exit, însă trebuie
avut grijă la astfel de proceduri să se termine în aceleași condiții. De ex. situația se complică
dacă impunem ca procedura trebuie să prezerve regiștri ca SI și DI, atunci ar fi mai avantajos
saltul spre sfârșit.

inițializare

NU sir1 se DA
termina?

sir2 se DA sir2 se DA
termina? termina?

NU NU
ZF=0, CF=0 ZF=1

ZF=0, CF=1

DA
DS:[SI]≠ES[DI]?

NU
ZF=0, CF=?
inc SI, DI; dec CX

NU
CX=0?

DA

terminare anormală

Terminarea anormală poate fi testată de CX=0. Observație: organigramele pot fi foarte


utile în limbaj de asamblare.

S-ar putea să vă placă și