Diferente pentru introducere-in-asamblare intre reviziile #3 si #37

Nu exista diferente intre titluri.

Diferente intre continut:

h1. Introducere in asamblare
 
(Creat de '_chris_11_':user/chris_11 la data de _2005-08-15_ categoria _Limbaje_, autor(i) _Botau Cristian_)
 
*Continut scurt:*
 Acest articol prezinta un limbaj mai putin folosit : limbajul de asamblare. Este folosit de cele mai multe ori pentru marirea vitezei de rulare a programelor. In prima sectiune sunt prezentate cateva avantaje / dezavantaje in folosirea acestui limbaj. Apoi sunt definite notiunile elementare ce trebuie stiute inainte de a trece la tratarea limbajului propriu-zis. De asemenea, sunt prezentate cateva intructiuni si transcrieri ale unor structuri din C/Pascal in limbaj de asamblare. In final sunt comparati timpii de executie intre varianta pascal si varianta assembler ale unei proceduri de Bubble Sort.
==Include(page="template/raw")==
 
Acest articol prezinta un limbaj mai putin folosit : limbajul de asamblare. Este folosit de cele mai multe ori pentru marirea vitezei de rulare a programelor. In prima sectiune sunt prezentate cateva avantaje / dezavantaje in folosirea acestui limbaj. Apoi sunt definite notiunile elementare ce trebuie stiute inainte de a trece la tratarea limbajului propriu-zis. De asemenea, sunt prezentate cateva intructiuni si transcrieri ale unor structuri din C/Pascal in limbaj de asamblare. In final sunt comparati timpii de executie intre varianta pascal si varianta assembler ale unei proceduri de Bubble Sort.
 
 
*Continut lung:*
Limbajul de asamblare : avantaje si dezavantaje in folosirea lui
 
 
 
Desi este unul dintre cele mai vechi limbaje de programare, limbajul de asamblare este incorporat si *n cele mai noi compilatoare cum ar fi Visual C++ si Delphi. Codul de asamblare este foarte apropiat de codul executabil al programelor, asamblorul avand rolul de a codifica instructiunile din limbaj de asamblare in cod masina.
 
In prezent, limbajul de asamblare este din ce in ce mai putin folosit in scrierea programelor, deoarece compilatoarele de ultima generatie au functiile cele mai folosite de programatori deja scrise si optimizate in limbaj de asamblare (gen memmove, memset / fillchar, etc.) incluse in unit-uri si biblioteci. Bineinteles, daca se doreste atingerea unor timpi de executie foarte mici, folosirea limbajului de asamblare este preferabila (cand avem algoritmul de complexitate optima).
 
Avantaje ale limbajului de asamblare
 
* Viteza foarte mare the executie a programelor (unele programe scrise si optimizate in asamblare au viteza de executie pana la 5-10 de ori mai mare decat cele scrise in C sau Pascal);
* Marimea codului executabil este foarte mica;
* Ajuta utilizatorul sa inteleaga mai bine cum functioneaza microprocesorul si sa scrie programe eficiente si in High Level Languages.
 
 
 
Dezavantaje ale limbajului de asamblare
 
* nu este portabil; un program scris pentru calculatoare IBM PC nu va rula pe un Apple Macintosh (difera unele instructiuni);
* un program in asamblare este mai greu de scris si ia mai mult timp decat un program scris in Pascal sau C;
* un program scris in asamblare este mai greu de depanat;
 
Majoritatea compilatoarelor din prezent permit programatorului sa scrie anumite parti de cod sau subprograme in asamblare, astfel incat programatorul sa poata scrie in asamblare doar partea din program care se executa de cele mai multe ori, programul ruland astfel mult mai rapid. Sunt si compilatoare dedicate exclusiv limbajului de asamblare, cum ar fi TASM - Turbo Assembler (il gasiti in directorul BP/Bin), MASM - Microsoft Assembler, etc.
 
Pentru a scrie o secventa de limbaj de asamblare intr-un program C se foloseste cuvantul rezervat asm :
 
asm { instructiuni in asamblare };
 
iar in pascal :
 
asm
cod asamblare
end.
 
 
 
 
 
Notiuni de baza ale limbajului de asamblare
 
Registrii
 
Limbajul de asamblare este foarte apropiat de codul executabil, deci pentru ca programatorul sa fie capabil sa scrie codul in asamblare trebuie sa cunoasca modul in care functioneaza microprocesorul si modul in care acesta executa instructiunile.
 
Toate operatiile aritmetice/logice/acces la memorie se fac prin intermediul registrilor. Registrii microprocesorului sunt asemanatori variabilelor (pot fi initializati cu anumite valori, se pot efectua operatii asupra lor, pot fi accesati (cititi/modificati)).
Registrii microprocesorului sunt clasificati in mai multe categorii :
 
* registri de uz general : eax, ebx, ecx, edx si subregistrii lor;
* registri segment : cs (code segment), ds (data segment), es (extra segment), ss (stack segment) - sunt folositi pentru adresarea memoriei in cazul modelului de memorie segmentat
* registrii index : esi, edi (source index, destination index);
* registri speciali : ebp, esp, ip;
 
Registrii de uz general sunt folositi in executia operatiilor aritmetice si logice. Registrii de segment si cei index sunt folositi in adresarea memoriei, iar cei speciali sunt folositi de microprocesor si S.O. pentru rularea programelor.
 
 
 
Registrii de uz general
 
Acestia sunt :
 
* eax * numit si "accumulator" * acesta este cel mai folosit in executarea operatiilor aritmetice (adunare, scadere, inmultire, impartire);
* ebx * numit si "base" * acesta registru este folosit pentru adresarea indirecta a memoriei;
* ecx * numit si "count" * acest registru este folosit mai ales in cadrul "buclelor" (in instructiunile repetitive * for/ while do/ do while/ repeat until);
* edx * numit si "data" * retine anumite variabile, si este folosit impreuna cu eax in executarea operatiei "div" * impartire intreaga (avand rolul de a retine restul impartirii).
 
Dupa cum ati observat, fiecare registru general are o functie specifica, insa oricare din ei pot fi folositi si la operatii aritmetice/logice, adresare memorie sau ca si variabile.
 
Registrii precedati de prefixul "e" sunt "pe 32 de biti", deci pot fi tratati ca si variabile unsigned long/signed long (doubleword). Acestia pot fi folositi si ca registri pe 16 biti (word) sau pe 8 biti (byte) prin intermediul "subregistrilor" : ax, bx, cx, dx (reprezinta word-ul nesemnificativ = cel din dreapta)., ah, bh, ch, dh (byte-ul cel mai semnificativ al ax, bx, cx, dx), al, bl, cl, dl(registrii pe 8 biti).
 
 
Registrii de segment si index
 
Acesti registri sunt folositi la adresarea memoriei.
De exemplu
Avem variabila void *x;(x : pointer in pascal);
pentru modelul de memorie segmentata, in adresarea memoriei adresate de x se folosesc intructiunile (vezi "Intructiunile de baza ale unui microprocesor 8086") :
 
 
 
// es:[di] va accesa adresa pointata de x;
 
les di, x
 
// se copiaza in ax primul word
 
// de la adresa es:[di]
 
mov ax, es:[di]
 
// se copiaza in al cel de-al 3-lea byte
 
// de la adresa es:[di]
 
mov al, es:[di+2]
 
Parantezele drepte se refera la zona de memorie adresata de registru.
Exemplu :
 
"[esi]" * se refera la valoarea aflata la adresa de memorie cu offset-ul esi, iar "esi" * se refera la valoarea stocata in registrul esi;
 
daca esi = 100 atunci [esi] * va reprezenta valoarea care se afla la adresa cu offset-ul 100, iar esi va reprezenta valoarea 100.
 
Va sfatuiesc sa NU incercati sa modificati urmatorii registri : cs (care contine segmentul la care se afla incarcat codul executabilului, ss * contine segmentul de memorie in care se afla stiva sistemului).
 
 
 
Registrii speciali
 
Sunt folositi foarte rar in scrierea efectiva a programelor, deci nu veti accesa prea des acesti registri.De exemplu, ip (instruction pointer) care retine adresa (de fapt offset-ul) la care se afla instructiunea care urmeaza sa fie executata.
 
 
 
Flagurile microprocesorului
 
Flag-urile microprocesorului sunt asemanatoare unor variabile boolene. Cu ajutorul lor putem afla rezultatul unei comparatii sau daca o operatie a produs "overflow", etc. Aceste flag-uri nu pot fi accesate direct ca si registrii, ci testarea daca un flag este activat se face prin intermediul "jump"-urilor conditionate.(vezi instructiunea cmp).
 
Exemplu :
 
Fie ax = 1. Decrementarea lui ax cu 1 va duce la activarea flagului numit Zero-Flag.(rezultatul operatiei a fost 0).
 
Fie ax = 65535 (0xFFFF). Incrementarea lui ax cu 2 va duce la setarea flagului numit Overflow-Flag (rezultatul operatiei nu "incape" in 16 biti);
 
 
 
 
 
Instructiunile de baza ale microprocesorului 8086 :
 
Sunt folosite urmatoarele prescurtari :
 
* reg8/ reg16/ reg32 * se refera la registri pe 8/16/32 biti;
* imm8/ imm16/ imm32 * se refera la valori imediate (constante);
* mem8/ mem16/ mem32 * zona memorie de memorie sau variabile pe 8/16/32 biti;
 
Scriere/Citire memorie si registri
 
Instructiunea MOV
 
Sintaxa
 
mov dest, source // echivalenta cu dest = source
 
Variante
 
mov reg8/16/32, mem8/16/32
mov reg8/16/32, reg8/16/32
mov mem8/16/32, reg8/16/32
mov reg8/16/32, imm8/16/32
mov mem8/16/32, imm8/16/32
 
Descriere
 
Instructiunea copiaza o valoare dintr-o locatie in alta locatie. Aceasta locatie poate fi zona de memorie, variabila, registru. De retinut este ca nu exista variante ale instructiunii care copiaza direct dintr-o zona de memorie in alta.
 
Flaguri afectate
 
Instructiunea mov nu modifica nici un flag.
 
Restrictii
 
Ambii operanzi trebuie sa aiba aceeasi marime. De exemplu pentru ultima varianta a instructiunii mov trebuie specificata marimea zonei de memorie. Instructiunea "mov [bx], 0" nu este corecta deoarece compilatorul nu stie ce vrea sa faca instructiunea: sa copieze valoarea 0 in byte-ul, in word-ul sau in doubleword-ul de la adresa bx. Astfel variantele corecte sunt:
 
mov byte ptr [bx], 0
 
mov word ptr [bx], 0
 
mov dword ptr [bx], 0
 
Exemplu
 
mov ax, 3 // ax = 3
mov bx, ax // bx = ax
 
 
 
Instructiuni aritmetice
 
Instructiunile ADD, SUB, INC, DEC
 
Sintaxa
 
add dest, source // echivalenta cu dest+=source
sub dest, source // echivalenta cu dest-=source
inc dest // echivalenta cu dest++
dec dest // echivalenta cu dest*
 
 
 
Variante
 
add(sub) reg8/16/32, mem8/16/32
add(sub) reg8/16/32, reg8/16/32
add(sub) mem8/16/32, reg8/16/32
add(sub) reg8/16/32, imm8/16/32
add(sub) mem8/16/32, imm8/16/32
 
Descriere
 
Instructiunea "add" este folosita pentru a aduna doua valori, "sub" pentru a scadea o valoare din alta, inc * incrementarea unei variabile, "dec" * decrementarea unei variabile (registru/memorie).
 
 
 
Flaguri afectate
 
Flagurile afectate de aceste instructiuni sunt :
 
carry flag - pentru "signed overflow"
 
overflow flag - pentru overflow
 
sign flag - activat daca rezultatul este negativ
 
zero flag - activat daca rezultatul operatiei a fost 0
 
parity flag - este setat in functie de paritatea rezultatului
 
 
 
Restrictii
 
Destinatia trebuie sa aiba aceeasi marime ca si sursa.
 
Exemplu
 
mov ax, 3 // ax = 3
add bx, ax // bx = ax+3
 
Instructiuni pe biti
 
Instructiunile AND, TEST, OR, XOR, NOT
 
Sintaxa
 
and dest, source // echivalenta cu dest&=source
 
test dest, source // dest & source
 
or dest, source // echivalenta cu dest|=source
 
xor dest, source // echivalenta cu dest^=source
 
not dest // ~dest
 
Variante
 
Pentru "and", "or", "test", "xor" sunt ca si cele de la add. Pentru "not" avem doua variante :
 
not reg8/16/32
 
not mem
 
 
 
Descriere
 
Executa operatiile pe biti corespunzatoare. Instructiunea test (non-destructive and) este asemanatoare lui and, dar nu afecteaza rezultatul ci doar flagurile.
 
 
 
Flaguri afectate
 
Instructiunea "not" nu afecteaza nici un flag.
 
Flagurile afectate de celalalte instructiuni sunt :
 
carry, overflow - le dezactiveaza
 
sign flag - este copiat bitul cel mai semnificativ din rezultat (semnul rezultatului, de fapt)
 
zero flag - activat daca rezultatul operatiei a fost 0
 
parity flag - este setat in functie de paritatea rezultatului
 
 
 
Restrictii
 
Destinatia trebuie sa aiba aceeasi marime ca si sursa.
Instructinile OR/XOR/INC/DEC modifica flag-urile microprocesorului: Carry(numai ADD/SUB), Overflow, Zero, Parity.
 
Exemplu
 
mov ax, 5 // ax = 3
test ax, 1 // daca (ax & 1)
jz @skip // daca ax este nu este impar sari la @skip
// executa instructiuni
@skip
and bx, FFFEh // bx = bx & 0xFFFE (se sterge
// bitul cel mai nesemnificativ)
 
 
 
Instructiunile PUSH/POP
 
Descriere
 
Aceste instructiuni sunt folosite pentru accesarea stivei sistemului. Instructiunea PUSH pune pe stiva sistemului o valoare, iar instructiunea POP extrage valoarea din varful stivei.
 
Sintaxa
 
push val
 
pop val
 
Variante
 
push reg8/16/32
 
pop reg8/16/32
 
Exemplu
 
@lp:
push cx // salvam valoarea cx
... // prelucreaza datele, se poate modifica cx
pop cx // scoate de pe stiva valoarea cx
dec cx
jnz @lp // daca (cx != 0) atunci sari inapoi la @lp
 
Instructiunea CMP
 
Descriere
 
Compara doua valori si seteaza flag-urile microprocesorului cu rezultatele compararii. De cele mai multe ori este folosita in combinatie cu jump-urile (vezi mai jos).
 
Sintaxa
 
cmp val1, val2
 
Variante
 
cmp reg8/16/32, reg8/16/32
 
cmp reg8/16/32, imm8/16/32
 
cmp mem8/16/32, reg8/16/32
 
cmp mem8/16/32, imm8/16/32
 
 
 
Jump-urile neconditionate (JMP) si jump-urile conditionate (JE, JNE, JB, JBE, JA, JAE, JZ, JNZ)
 
Aceste instructiuni sunt folosite pentru generarea instructiunilor If /while do /do while /repeat /for.Sunt asemanatoare instructiunii goto din C/Pascal.
Sintaxa : instructiune label
JE * "sare" cu executia programului la label daca flag-ul equality este setat;
JNE * "sare" cu executia programului la label daca flag-ul equality nu este setat;
JB * "sare" cu executia programului la label daca flag-ul below este setat;
JBE * "sare" cu executia programului la label daca flag-ul below sau flagul equality este setat;
JA * "sare" cu executia programului la label daca flag-ul below nu este setat;
JAE * "sare" cu executia programului la label daca flag-ul equality este setat sau flag-ul below nu este setat;
JZ * "sare" cu executia programului la label daca flag-ul zero este setat;
JNZ * "sare" cu executia programului la label daca flag-ul zero nu este setat;
JMP * "sare" cu executia programului la label;
 
Exemplu :
 
mov ax, 1
@loop :
instr1
instr2
*
inc ax
 
// compara ax cu valoarea 10
 
// si seteaza flagurile
cmp ax, 10
 
// daca ax != 10 sari inapoi la @loop
jne @loop
 
Exemplul 2:
 
instr1
jmp @label1
instr2
instr3
@label1 :
 
 
 
In ultimul exemplu instr2 si instr3 nu vor fi executate deoarece inaintea lor se afla instructiunea jump care face ca programul sa sara cu executia la instructiunea imediat urmatoare etichetei "label1".
 
 
 
Instructiunea LOOP
 
Instructiunea "loop" este folosita de obicei in transcrierea for-urilor si a buclelor.
 
Sintaxa
 
loop label
 
Descriere
 
Echivalenta cu :
 
dec ecx (sau dec cx)
jz label
 
Instructiunea INT
 
Descriere
 
Instructiunea INT apealeaza o intrerupere.
Sintaxa
INT nr_intrerupere (un numar cuprins intre 0 si 255);
 
Intreruperile opresc programul din rulare, executa un anumit cod si apoi permit continuarea rularii programului. Cu ajutorul apelului intreruperilor aplicatia poate sa comunice cu sistemul de operare, si cu unele componente hardware ale calculatorului (mouse-ul, tastatura, imprimanta, etc). Fiecare intrerupere are asociat un numar.
Exemple de intreruperi :
0x9 : intreruperea pentru tastatura;
0x10 : intreruperea care ofera serviciile BIOS pentru accesul la placa video;
0x21 : intreruperea sistemului de operare DOS;
0x33 : intreruperea pentru mouse;
 
In DOS pentru initializarea mouse-ului se va apela
 
functia numarul 0 a intreruperii 0x33 :
asm
{
mov ax, 0
 
// seteaza registrul ax cu valoarea 0 pentru ca
 
// driverul ce gestioneaza intreruperea 0x33
//sa stie ca trebuie sa initializeze mouse-ul
int 0x33
}
 
Pentru afisarea mouse-ului :
asm
{
mov ax, 1
int 0x33
}
 
Pentru ascunderea cursorului de la mouse :
asm
{
mov ax, 2
int 0x33;
}
 
 
 
 
 
Transcrierea unor instructiuni C/PASCAL in asamblare
 
 
 
* IF cond THEN instr
 
Exemplu : if (a==5) b=a; // a, b * integer
 
mov ax, a
cmp ax, 5
 
// ne intrebam daca !cond
jnz @skip_if
mov b, ax
@skip_if :
 
* IF cond THEN instr1 ELSE instr2
 
Exemplu : if (a==b) a-=b; else b-=a;
 
Transcrierea in asamblare :
 
mov ax, a
mov bx, b
 
// compara ax cu bx si seteaza flag-urile
 
cmp ax, bx
 
je @if_then
 
// if (ax == bx) sari la @if_then
sub b, ax
jmp @end_if :
@if_then :
sub a, bx
@end_if :
 
 
 
* DO { inst } WHILE
 
Exemplu :
 
int i, a, n;
i = 0;
a = 0;
do
{
i++;
a+=I;
} while (i<=n);
 
 
 
Codul in asamblare :
 
// cx = 0, instructiunea xor cx, cx este
 
// mai rapida decat mov cx, 0
xor cx, cx
 
xor ax, ax
mov bx, n
@while :
inc cx
add ax, cx
cmp cx, bx
jne @while
mov a, ax
mov I, cx
 
 
Sau folosind instructiunea loop (pornim invers) :
 
mov cx, n
 
@while:
add ax, cx
loop @while
 
In codul de mai sus (prima varianta) puteti observa cateva mici optimizari : retinerea variabilelor in registri (accesul la registri este mult mai rapid decat la memorie), inlocuirea instructiunii mov reg, 0 cu xor reg, reg. Varianta "xor" este mai rapida (desi mov si xor "teoretic" au tot atatea ceasuri de procesor) deoarece instructiunea mov reg, 0 transcrisa in cod masina este mai "lunga" (are cu 2-4 bytes mai mult decat xor reg, reg). Aceeasi regula se aplica si in cazul "or reg, reg" in testarea daca valoarea unui registru este egala cu 0, intrucat instructiunea "or" modifica flagurile procesorului (ZeroFlag). O alta optimizare este folosirea registrului ax pentru calcule (in majoritatea cazurilor instructiunile care folosesc registrul (e)ax sunt mai rapide).
 
 
 
 
 
Program demonstrativ
 
Exemplu de functie in realizata in C/Asamblare (compilator folosit BC++ 3.1):
 
// BSort.CPP
#include <stdio.h>
#include <conio.h>
 
int a[20], n;
 
void bsort()
{
asm {
mov di,seg a
mov es,di
mov di,offset a
// es:[di] inceputul vectorului
// es - adresa segment
// di - adresa offset
};
 
do_while_not_ok :
asm {
// ok = 1;
mov dx, 1
// cx = n;
mov cx, n
// cx = n-1
dec cx
// ax = a[0]
mov ax, es:[di]
// punem di pe stiva
push di
}
inner_for :
asm {
// bx = a[i-1]
mov bx, ax
// ne mutam pe noua pozitie in vector
add di, 2
// ax = a[i]
mov ax, es:[di]
// comparam ax cu bx (a[i-1] cu a[i])
cmp bx, ax
// if (a[i-1] <= a[i]) don't swap
jbe if_not
// interschimbam a[i-1] cu a[i]
xchg bx, ax
// le scriem in memorie
mov es:[di], ax
mov es:[di-2], bx
// ok = 0
xor dx, dx
}
if_not :
asm {
// cx--;
dec cx
jnz inner_for
// daca cx != 0 atunci continuam for-ul
pop di
// compara dx cu 0
// (mai rapid decat cmp dx, 0)
or dx, dx
// if (ok) executam inca odata while-ul
jz do_while_not_ok
}
}
 
void citire()
{
printf("Numarul de elemente : ");
scanf("%d", &n);
for (int i=0; i<n; i++)
{
printf("a[%d] = ", i);
scanf("%d", &a[i]);
}
}
 
void afis()
{
for (int i=0; i<n; i++)
printf("%d ", a[i]);
}
 
int main()
{
citire();
bsort();
afis();
return 0;
}
 
Varianta Pascal + Asm (compilata cu BP 7.0):
 
program b_sort;
 
const
max_N = 1000;
n_tests = 100;
 
var
N : Integer;
a, b : array [1..max_N] of Integer;
 
procedure citire;
var
f : Text;
i : Integer;
begin
Assign(f, 'bsort.in'); Reset(f);
readln(f, N);
for i := 1 to N do
read(f, b[i]);
Close(f)
end;
 
procedure flip;
var
i : Integer;
begin
for i:=1 to N do
a[i] := b[i];
end;
 
var p : pointer;
 
procedure bsort; assembler;
asm
{ les <=> es = seg(p), di = offset(p) }
les di, p
@do_while_not_ok :
mov dx, 1
mov cx, N
dec cx
mov ax, es:[di]
push di
@inner_for :
mov bx, ax
add di, 2
mov ax, es:[di]
cmp bx, ax
jbe @if_not
xchg bx, ax
mov es:[di], ax
mov es:[di-2], bx
xor dx, dx
@if_not :
dec cx
jnz @inner_for
pop di
or dx, dx
jz @do_while_not_ok
end;
 
procedure bsort2;
var
ok : Boolean;
i, aux : Integer;
begin
repeat
ok := true;
for i := 1 to n-1 do
if (a[i] > a[i+1]) then
begin
aux := a[i];
a[i] := a[i+1];
a[i+1] := aux;
ok := false;
end;
until ok;
end;
 
var
i : Integer;
starttick, endtick : LongInt;
time : Longint absolute $0000:$046C;
 
{ timer pentru masurarea timpului }
const tickspersecond = 18.206;
 
procedure starttimer;
begin
starttick := time;
end;
 
function elapsedtime : real;
begin
endtick := time;
elapsedtime := (endtick - starttick)*
(1 / tickspersecond);
end;
 
begin
citire;
p := @a;
 
starttimer;
for i:=1 to n_tests do
begin
flip;
bsort;
end;
writeln(n_tests, ' rulari ale bsort in asamblare
au rulat in : ', elapsedtime : 6:3, ' secunde');
 
starttimer;
for i:=1 to n_tests do
begin
flip;
bsort2;
end;
writeln(n_tests, ' rulari ale bsort obisnuit
 
au rulat in : ',
elapsedtime : 6:3, ' secunde');
end.
 
Output program pascal (pe pc-ul meu: Duron 750 Mhz):
100 rulari ale bsort in asamblare au rulat in : 1.263 secunde
100 rulari ale bsort obisnuit au rulat in : 4.614 secunde
 
De precizat este ca nu am optimizat programul aproape deloc (am transcris codul din pascal in asm). Cu optimizari "hardcore" se pot obtine timpi mult mai buni. Diferenta dintre varianta asm si varianta high level language este mai mica pe compilatoarele mai noi, cum ar fi GCC sau FreePascal deoarece acestea stiu genera un cod mai optimizat. Totusi, nici un compilator nu poate bate creierul uman.
 
 
 
 
 
Linkuri
 
[1]http://www.arl.wustl.edu/~lockwood/class/cs306/books/
 
artofasm/toc.html
 
 
 
Cartea "The Art of Assembly Language Programming". Este detaliata. Foarte buna atat pentru incepatori cat si pentru avansati.
 
[2]http://www.goof.com/pcg/doc/pentopt.txt
 
 
 
Un tutorial bun pentru optimizarea programelor in asamblare pentru procesoarele din familia Pentium. Tutorialul este pentru avansati.
 
[3]http://www.techtutorials.info/assembly.html
 
 
 
Mai multe tutoriale despre assembly language.
 
Doua cuvinte conclusive...
 
Acest articol trateaza "bazele" programarii in asamblare, dupa cum ii zice numele este doar o introducere. Pentru a sti sa programezi in asm, trebuie invatate mult mai multe lucruri. Scopul articolului este de a starni interesul cititorilor si de a le oferi o imagine asupra acestui limbaj. Cei ce doresc sa invete mai multe despre assembly language sunt invitati sa citeasca cartile / tutorialele din sectiunea linkuri.
 
References
 
Visible links
1. http://www.arl.wustl.edu/%7elockwood/class/cs306/books/artofasm/toc.html
2. http://www.goof.com/pcg/doc/pentopt.txt
3. http://www.techtutorials.info/assembly.html
==Include(page="template/raw")==
 
Limbajul de asamblare : avantaje si dezavantaje in folosirea lui
 
 
 
Desi este unul dintre cele mai vechi limbaje de programare, limbajul de asamblare este incorporat si *n cele mai noi compilatoare cum ar fi Visual C++ si Delphi. Codul de asamblare este foarte apropiat de codul executabil al programelor, asamblorul avand rolul de a codifica instructiunile din limbaj de asamblare in cod masina.
 
In prezent, limbajul de asamblare este din ce in ce mai putin folosit in scrierea programelor, deoarece compilatoarele de ultima generatie au functiile cele mai folosite de programatori deja scrise si optimizate in limbaj de asamblare (gen memmove, memset / fillchar, etc.) incluse in unit-uri si biblioteci. Bineinteles, daca se doreste atingerea unor timpi de executie foarte mici, folosirea limbajului de asamblare este preferabila (cand avem algoritmul de complexitate optima).
 
Avantaje ale limbajului de asamblare
 
* Viteza foarte mare the executie a programelor (unele programe scrise si optimizate in asamblare au viteza de executie pana la 5-10 de ori mai mare decat cele scrise in C sau Pascal);
* Marimea codului executabil este foarte mica;
* Ajuta utilizatorul sa inteleaga mai bine cum functioneaza microprocesorul si sa scrie programe eficiente si in High Level Languages.
 
 
 
Dezavantaje ale limbajului de asamblare
 
* nu este portabil; un program scris pentru calculatoare IBM PC nu va rula pe un Apple Macintosh (difera unele instructiuni);
* un program in asamblare este mai greu de scris si ia mai mult timp decat un program scris in Pascal sau C;
* un program scris in asamblare este mai greu de depanat;
 
Majoritatea compilatoarelor din prezent permit programatorului sa scrie anumite parti de cod sau subprograme in asamblare, astfel incat programatorul sa poata scrie in asamblare doar partea din program care se executa de cele mai multe ori, programul ruland astfel mult mai rapid. Sunt si compilatoare dedicate exclusiv limbajului de asamblare, cum ar fi TASM - Turbo Assembler (il gasiti in directorul BP/Bin), MASM - Microsoft Assembler, etc.
 
Pentru a scrie o secventa de limbaj de asamblare intr-un program C se foloseste cuvantul rezervat asm :
 
asm { instructiuni in asamblare };
 
iar in pascal :
 
asm
cod asamblare
end.
 
 
 
 
 
Notiuni de baza ale limbajului de asamblare
 
Registrii
 
Limbajul de asamblare este foarte apropiat de codul executabil, deci pentru ca programatorul sa fie capabil sa scrie codul in asamblare trebuie sa cunoasca modul in care functioneaza microprocesorul si modul in care acesta executa instructiunile.
 
Toate operatiile aritmetice/logice/acces la memorie se fac prin intermediul registrilor. Registrii microprocesorului sunt asemanatori variabilelor (pot fi initializati cu anumite valori, se pot efectua operatii asupra lor, pot fi accesati (cititi/modificati)).
Registrii microprocesorului sunt clasificati in mai multe categorii :
 
* registri de uz general : eax, ebx, ecx, edx si subregistrii lor;
* registri segment : cs (code segment), ds (data segment), es (extra segment), ss (stack segment) - sunt folositi pentru adresarea memoriei in cazul modelului de memorie segmentat
* registrii index : esi, edi (source index, destination index);
* registri speciali : ebp, esp, ip;
 
Registrii de uz general sunt folositi in executia operatiilor aritmetice si logice. Registrii de segment si cei index sunt folositi in adresarea memoriei, iar cei speciali sunt folositi de microprocesor si S.O. pentru rularea programelor.
 
 
 
Registrii de uz general
 
Acestia sunt :
 
* eax * numit si "accumulator" * acesta este cel mai folosit in executarea operatiilor aritmetice (adunare, scadere, inmultire, impartire);
* ebx * numit si "base" * acesta registru este folosit pentru adresarea indirecta a memoriei;
* ecx * numit si "count" * acest registru este folosit mai ales in cadrul "buclelor" (in instructiunile repetitive * for/ while do/ do while/ repeat until);
* edx * numit si "data" * retine anumite variabile, si este folosit impreuna cu eax in executarea operatiei "div" * impartire intreaga (avand rolul de a retine restul impartirii).
 
Dupa cum ati observat, fiecare registru general are o functie specifica, insa oricare din ei pot fi folositi si la operatii aritmetice/logice, adresare memorie sau ca si variabile.
 
Registrii precedati de prefixul "e" sunt "pe 32 de biti", deci pot fi tratati ca si variabile unsigned long/signed long (doubleword). Acestia pot fi folositi si ca registri pe 16 biti (word) sau pe 8 biti (byte) prin intermediul "subregistrilor" : ax, bx, cx, dx (reprezinta word-ul nesemnificativ = cel din dreapta)., ah, bh, ch, dh (byte-ul cel mai semnificativ al ax, bx, cx, dx), al, bl, cl, dl(registrii pe 8 biti).
 
 
Registrii de segment si index
 
Acesti registri sunt folositi la adresarea memoriei.
De exemplu
Avem variabila void *x;(x : pointer in pascal);
pentru modelul de memorie segmentata, in adresarea memoriei adresate de x se folosesc intructiunile (vezi "Intructiunile de baza ale unui microprocesor 8086") :
 
 
 
// es:[di] va accesa adresa pointata de x;
 
les di, x
 
// se copiaza in ax primul word
 
// de la adresa es:[di]
 
mov ax, es:[di]
 
// se copiaza in al cel de-al 3-lea byte
 
// de la adresa es:[di]
 
mov al, es:[di+2]
 
Parantezele drepte se refera la zona de memorie adresata de registru.
Exemplu :
 
"[esi]" * se refera la valoarea aflata la adresa de memorie cu offset-ul esi, iar "esi" * se refera la valoarea stocata in registrul esi;
 
daca esi = 100 atunci [esi] * va reprezenta valoarea care se afla la adresa cu offset-ul 100, iar esi va reprezenta valoarea 100.
 
Va sfatuiesc sa NU incercati sa modificati urmatorii registri : cs (care contine segmentul la care se afla incarcat codul executabilului, ss * contine segmentul de memorie in care se afla stiva sistemului).
 
 
 
Registrii speciali
 
Sunt folositi foarte rar in scrierea efectiva a programelor, deci nu veti accesa prea des acesti registri.De exemplu, ip (instruction pointer) care retine adresa (de fapt offset-ul) la care se afla instructiunea care urmeaza sa fie executata.
 
 
 
Flagurile microprocesorului
 
Flag-urile microprocesorului sunt asemanatoare unor variabile boolene. Cu ajutorul lor putem afla rezultatul unei comparatii sau daca o operatie a produs "overflow", etc. Aceste flag-uri nu pot fi accesate direct ca si registrii, ci testarea daca un flag este activat se face prin intermediul "jump"-urilor conditionate.(vezi instructiunea cmp).
 
Exemplu :
 
Fie ax = 1. Decrementarea lui ax cu 1 va duce la activarea flagului numit Zero-Flag.(rezultatul operatiei a fost 0).
 
Fie ax = 65535 (0xFFFF). Incrementarea lui ax cu 2 va duce la setarea flagului numit Overflow-Flag (rezultatul operatiei nu "incape" in 16 biti);
 
 
 
 
 
Instructiunile de baza ale microprocesorului 8086 :
 
Sunt folosite urmatoarele prescurtari :
 
* reg8/ reg16/ reg32 * se refera la registri pe 8/16/32 biti;
* imm8/ imm16/ imm32 * se refera la valori imediate (constante);
* mem8/ mem16/ mem32 * zona memorie de memorie sau variabile pe 8/16/32 biti;
 
Scriere/Citire memorie si registri
 
Instructiunea MOV
 
Sintaxa
 
mov dest, source // echivalenta cu dest = source
 
Variante
 
mov reg8/16/32, mem8/16/32
mov reg8/16/32, reg8/16/32
mov mem8/16/32, reg8/16/32
mov reg8/16/32, imm8/16/32
mov mem8/16/32, imm8/16/32
 
Descriere
 
Instructiunea copiaza o valoare dintr-o locatie in alta locatie. Aceasta locatie poate fi zona de memorie, variabila, registru. De retinut este ca nu exista variante ale instructiunii care copiaza direct dintr-o zona de memorie in alta.
 
Flaguri afectate
 
Instructiunea mov nu modifica nici un flag.
 
Restrictii
 
Ambii operanzi trebuie sa aiba aceeasi marime. De exemplu pentru ultima varianta a instructiunii mov trebuie specificata marimea zonei de memorie. Instructiunea "mov [bx], 0" nu este corecta deoarece compilatorul nu stie ce vrea sa faca instructiunea: sa copieze valoarea 0 in byte-ul, in word-ul sau in doubleword-ul de la adresa bx. Astfel variantele corecte sunt:
 
mov byte ptr [bx], 0
 
mov word ptr [bx], 0
 
mov dword ptr [bx], 0
 
Exemplu
 
mov ax, 3 // ax = 3
mov bx, ax // bx = ax
 
 
 
Instructiuni aritmetice
 
Instructiunile ADD, SUB, INC, DEC
 
Sintaxa
 
add dest, source // echivalenta cu dest+=source
sub dest, source // echivalenta cu dest-=source
inc dest // echivalenta cu dest++
dec dest // echivalenta cu dest*
 
 
 
Variante
 
add(sub) reg8/16/32, mem8/16/32
add(sub) reg8/16/32, reg8/16/32
add(sub) mem8/16/32, reg8/16/32
add(sub) reg8/16/32, imm8/16/32
add(sub) mem8/16/32, imm8/16/32
 
Descriere
 
Instructiunea "add" este folosita pentru a aduna doua valori, "sub" pentru a scadea o valoare din alta, inc * incrementarea unei variabile, "dec" * decrementarea unei variabile (registru/memorie).
 
 
 
Flaguri afectate
 
Flagurile afectate de aceste instructiuni sunt :
 
carry flag - pentru "signed overflow"
 
overflow flag - pentru overflow
 
sign flag - activat daca rezultatul este negativ
 
zero flag - activat daca rezultatul operatiei a fost 0
 
parity flag - este setat in functie de paritatea rezultatului
 
 
 
Restrictii
 
Destinatia trebuie sa aiba aceeasi marime ca si sursa.
 
Exemplu
 
mov ax, 3 // ax = 3
add bx, ax // bx = ax+3
 
Instructiuni pe biti
 
Instructiunile AND, TEST, OR, XOR, NOT
 
Sintaxa
 
and dest, source // echivalenta cu dest&=source
 
test dest, source // dest & source
 
or dest, source // echivalenta cu dest|=source
 
xor dest, source // echivalenta cu dest^=source
 
not dest // ~dest
 
Variante
 
Pentru "and", "or", "test", "xor" sunt ca si cele de la add. Pentru "not" avem doua variante :
 
not reg8/16/32
 
not mem
 
 
 
Descriere
 
Executa operatiile pe biti corespunzatoare. Instructiunea test (non-destructive and) este asemanatoare lui and, dar nu afecteaza rezultatul ci doar flagurile.
 
 
 
Flaguri afectate
 
Instructiunea "not" nu afecteaza nici un flag.
 
Flagurile afectate de celalalte instructiuni sunt :
 
carry, overflow - le dezactiveaza
 
sign flag - este copiat bitul cel mai semnificativ din rezultat (semnul rezultatului, de fapt)
 
zero flag - activat daca rezultatul operatiei a fost 0
 
parity flag - este setat in functie de paritatea rezultatului
 
 
 
Restrictii
 
Destinatia trebuie sa aiba aceeasi marime ca si sursa.
Instructinile OR/XOR/INC/DEC modifica flag-urile microprocesorului: Carry(numai ADD/SUB), Overflow, Zero, Parity.
 
Exemplu
 
mov ax, 5 // ax = 3
test ax, 1 // daca (ax & 1)
jz @skip // daca ax este nu este impar sari la @skip
// executa instructiuni
@skip
and bx, FFFEh // bx = bx & 0xFFFE (se sterge
// bitul cel mai nesemnificativ)
 
 
 
Instructiunile PUSH/POP
 
Descriere
 
Aceste instructiuni sunt folosite pentru accesarea stivei sistemului. Instructiunea PUSH pune pe stiva sistemului o valoare, iar instructiunea POP extrage valoarea din varful stivei.
 
Sintaxa
 
push val
 
pop val
 
Variante
 
push reg8/16/32
 
pop reg8/16/32
 
Exemplu
 
@lp:
push cx // salvam valoarea cx
... // prelucreaza datele, se poate modifica cx
pop cx // scoate de pe stiva valoarea cx
dec cx
jnz @lp // daca (cx != 0) atunci sari inapoi la @lp
 
Instructiunea CMP
 
Descriere
 
Compara doua valori si seteaza flag-urile microprocesorului cu rezultatele compararii. De cele mai multe ori este folosita in combinatie cu jump-urile (vezi mai jos).
 
Sintaxa
 
cmp val1, val2
 
Variante
 
cmp reg8/16/32, reg8/16/32
 
cmp reg8/16/32, imm8/16/32
 
cmp mem8/16/32, reg8/16/32
 
cmp mem8/16/32, imm8/16/32
 
 
 
Jump-urile neconditionate (JMP) si jump-urile conditionate (JE, JNE, JB, JBE, JA, JAE, JZ, JNZ)
 
Aceste instructiuni sunt folosite pentru generarea instructiunilor If /while do /do while /repeat /for.Sunt asemanatoare instructiunii goto din C/Pascal.
Sintaxa : instructiune label
JE * "sare" cu executia programului la label daca flag-ul equality este setat;
JNE * "sare" cu executia programului la label daca flag-ul equality nu este setat;
JB * "sare" cu executia programului la label daca flag-ul below este setat;
JBE * "sare" cu executia programului la label daca flag-ul below sau flagul equality este setat;
JA * "sare" cu executia programului la label daca flag-ul below nu este setat;
JAE * "sare" cu executia programului la label daca flag-ul equality este setat sau flag-ul below nu este setat;
JZ * "sare" cu executia programului la label daca flag-ul zero este setat;
JNZ * "sare" cu executia programului la label daca flag-ul zero nu este setat;
JMP * "sare" cu executia programului la label;
 
Exemplu :
 
mov ax, 1
@loop :
instr1
instr2
*
inc ax
 
// compara ax cu valoarea 10
 
// si seteaza flagurile
cmp ax, 10
 
// daca ax != 10 sari inapoi la @loop
jne @loop
 
Exemplul 2:
 
instr1
jmp @label1
instr2
instr3
@label1 :
 
 
 
In ultimul exemplu instr2 si instr3 nu vor fi executate deoarece inaintea lor se afla instructiunea jump care face ca programul sa sara cu executia la instructiunea imediat urmatoare etichetei "label1".
 
 
 
Instructiunea LOOP
 
Instructiunea "loop" este folosita de obicei in transcrierea for-urilor si a buclelor.
 
Sintaxa
 
loop label
 
Descriere
 
Echivalenta cu :
 
dec ecx (sau dec cx)
jz label
 
Instructiunea INT
 
Descriere
 
Instructiunea INT apealeaza o intrerupere.
Sintaxa
INT nr_intrerupere (un numar cuprins intre 0 si 255);
 
Intreruperile opresc programul din rulare, executa un anumit cod si apoi permit continuarea rularii programului. Cu ajutorul apelului intreruperilor aplicatia poate sa comunice cu sistemul de operare, si cu unele componente hardware ale calculatorului (mouse-ul, tastatura, imprimanta, etc). Fiecare intrerupere are asociat un numar.
Exemple de intreruperi :
0x9 : intreruperea pentru tastatura;
0x10 : intreruperea care ofera serviciile BIOS pentru accesul la placa video;
0x21 : intreruperea sistemului de operare DOS;
0x33 : intreruperea pentru mouse;
 
In DOS pentru initializarea mouse-ului se va apela
 
functia numarul 0 a intreruperii 0x33 :
asm
{
mov ax, 0
 
// seteaza registrul ax cu valoarea 0 pentru ca
 
// driverul ce gestioneaza intreruperea 0x33
//sa stie ca trebuie sa initializeze mouse-ul
int 0x33
}
 
Pentru afisarea mouse-ului :
asm
{
mov ax, 1
int 0x33
}
 
Pentru ascunderea cursorului de la mouse :
asm
{
mov ax, 2
int 0x33;
}
 
 
 
 
 
Transcrierea unor instructiuni C/PASCAL in asamblare
 
 
 
* IF cond THEN instr
 
Exemplu : if (a==5) b=a; // a, b * integer
 
mov ax, a
cmp ax, 5
 
// ne intrebam daca !cond
jnz @skip_if
mov b, ax
@skip_if :
 
* IF cond THEN instr1 ELSE instr2
 
Exemplu : if (a==b) a-=b; else b-=a;
 
Transcrierea in asamblare :
 
mov ax, a
mov bx, b
 
// compara ax cu bx si seteaza flag-urile
 
cmp ax, bx
 
je @if_then
 
// if (ax == bx) sari la @if_then
sub b, ax
jmp @end_if :
@if_then :
sub a, bx
@end_if :
 
 
 
* DO { inst } WHILE
 
Exemplu :
 
int i, a, n;
i = 0;
a = 0;
do
{
i++;
a+=I;
} while (i<=n);
 
 
 
Codul in asamblare :
 
// cx = 0, instructiunea xor cx, cx este
 
// mai rapida decat mov cx, 0
xor cx, cx
 
xor ax, ax
mov bx, n
@while :
inc cx
add ax, cx
cmp cx, bx
jne @while
mov a, ax
mov I, cx
 
 
Sau folosind instructiunea loop (pornim invers) :
 
mov cx, n
 
@while:
add ax, cx
loop @while
 
In codul de mai sus (prima varianta) puteti observa cateva mici optimizari : retinerea variabilelor in registri (accesul la registri este mult mai rapid decat la memorie), inlocuirea instructiunii mov reg, 0 cu xor reg, reg. Varianta "xor" este mai rapida (desi mov si xor "teoretic" au tot atatea ceasuri de procesor) deoarece instructiunea mov reg, 0 transcrisa in cod masina este mai "lunga" (are cu 2-4 bytes mai mult decat xor reg, reg). Aceeasi regula se aplica si in cazul "or reg, reg" in testarea daca valoarea unui registru este egala cu 0, intrucat instructiunea "or" modifica flagurile procesorului (ZeroFlag). O alta optimizare este folosirea registrului ax pentru calcule (in majoritatea cazurilor instructiunile care folosesc registrul (e)ax sunt mai rapide).
 
 
 
 
 
Program demonstrativ
 
Exemplu de functie in realizata in C/Asamblare (compilator folosit BC++ 3.1):
 
// BSort.CPP
#include <stdio.h>
#include <conio.h>
 
int a[20], n;
 
void bsort()
{
asm {
mov di,seg a
mov es,di
mov di,offset a
// es:[di] inceputul vectorului
// es - adresa segment
// di - adresa offset
};
 
do_while_not_ok :
asm {
// ok = 1;
mov dx, 1
// cx = n;
mov cx, n
// cx = n-1
dec cx
// ax = a[0]
mov ax, es:[di]
// punem di pe stiva
push di
}
inner_for :
asm {
// bx = a[i-1]
mov bx, ax
// ne mutam pe noua pozitie in vector
add di, 2
// ax = a[i]
mov ax, es:[di]
// comparam ax cu bx (a[i-1] cu a[i])
cmp bx, ax
// if (a[i-1] <= a[i]) don't swap
jbe if_not
// interschimbam a[i-1] cu a[i]
xchg bx, ax
// le scriem in memorie
mov es:[di], ax
mov es:[di-2], bx
// ok = 0
xor dx, dx
}
if_not :
asm {
// cx--;
dec cx
jnz inner_for
// daca cx != 0 atunci continuam for-ul
pop di
// compara dx cu 0
// (mai rapid decat cmp dx, 0)
or dx, dx
// if (ok) executam inca odata while-ul
jz do_while_not_ok
}
}
 
void citire()
{
printf("Numarul de elemente : ");
scanf("%d", &n);
for (int i=0; i<n; i++)
{
printf("a[%d] = ", i);
scanf("%d", &a[i]);
}
}
 
void afis()
{
for (int i=0; i<n; i++)
printf("%d ", a[i]);
}
 
int main()
{
citire();
bsort();
afis();
return 0;
}
 
Varianta Pascal + Asm (compilata cu BP 7.0):
 
program b_sort;
 
const
max_N = 1000;
n_tests = 100;
 
var
N : Integer;
a, b : array [1..max_N] of Integer;
 
procedure citire;
var
f : Text;
i : Integer;
begin
Assign(f, 'bsort.in'); Reset(f);
readln(f, N);
for i := 1 to N do
read(f, b[i]);
Close(f)
end;
 
procedure flip;
var
i : Integer;
begin
for i:=1 to N do
a[i] := b[i];
end;
 
var p : pointer;
 
procedure bsort; assembler;
asm
{ les <=> es = seg(p), di = offset(p) }
les di, p
@do_while_not_ok :
mov dx, 1
mov cx, N
dec cx
mov ax, es:[di]
push di
@inner_for :
mov bx, ax
add di, 2
mov ax, es:[di]
cmp bx, ax
jbe @if_not
xchg bx, ax
mov es:[di], ax
mov es:[di-2], bx
xor dx, dx
@if_not :
dec cx
jnz @inner_for
pop di
or dx, dx
jz @do_while_not_ok
end;
 
procedure bsort2;
var
ok : Boolean;
i, aux : Integer;
begin
repeat
ok := true;
for i := 1 to n-1 do
if (a[i] > a[i+1]) then
begin
aux := a[i];
a[i] := a[i+1];
a[i+1] := aux;
ok := false;
end;
until ok;
end;
 
var
i : Integer;
starttick, endtick : LongInt;
time : Longint absolute $0000:$046C;
 
{ timer pentru masurarea timpului }
const tickspersecond = 18.206;
 
procedure starttimer;
begin
starttick := time;
end;
 
function elapsedtime : real;
begin
endtick := time;
elapsedtime := (endtick - starttick)*
(1 / tickspersecond);
end;
 
begin
citire;
p := @a;
 
starttimer;
for i:=1 to n_tests do
begin
flip;
bsort;
end;
writeln(n_tests, ' rulari ale bsort in asamblare
au rulat in : ', elapsedtime : 6:3, ' secunde');
 
starttimer;
for i:=1 to n_tests do
begin
flip;
bsort2;
end;
writeln(n_tests, ' rulari ale bsort obisnuit
 
au rulat in : ',
elapsedtime : 6:3, ' secunde');
end.
 
Output program pascal (pe pc-ul meu: Duron 750 Mhz):
100 rulari ale bsort in asamblare au rulat in : 1.263 secunde
100 rulari ale bsort obisnuit au rulat in : 4.614 secunde
 
De precizat este ca nu am optimizat programul aproape deloc (am transcris codul din pascal in asm). Cu optimizari "hardcore" se pot obtine timpi mult mai buni. Diferenta dintre varianta asm si varianta high level language este mai mica pe compilatoarele mai noi, cum ar fi GCC sau FreePascal deoarece acestea stiu genera un cod mai optimizat. Totusi, nici un compilator nu poate bate creierul uman.
 
 
 
 
 
Linkuri
 
[1]http://www.arl.wustl.edu/~lockwood/class/cs306/books/
 
artofasm/toc.html
 
 
 
Cartea "The Art of Assembly Language Programming". Este detaliata. Foarte buna atat pentru incepatori cat si pentru avansati.
 
[2]http://www.goof.com/pcg/doc/pentopt.txt
 
 
 
Un tutorial bun pentru optimizarea programelor in asamblare pentru procesoarele din familia Pentium. Tutorialul este pentru avansati.
 
[3]http://www.techtutorials.info/assembly.html
 
 
 
Mai multe tutoriale despre assembly language.
 
Doua cuvinte conclusive...
 
Acest articol trateaza "bazele" programarii in asamblare, dupa cum ii zice numele este doar o introducere. Pentru a sti sa programezi in asm, trebuie invatate mult mai multe lucruri. Scopul articolului este de a starni interesul cititorilor si de a le oferi o imagine asupra acestui limbaj. Cei ce doresc sa invete mai multe despre assembly language sunt invitati sa citeasca cartile / tutorialele din sectiunea linkuri.
 
References
 
Visible links
1. http://www.arl.wustl.edu/%7elockwood/class/cs306/books/artofasm/toc.html
2. http://www.goof.com/pcg/doc/pentopt.txt
3. http://www.techtutorials.info/assembly.html
 
h1. Introducere in asamblare
 
(Categoria _Limbaje de programare_, Autor _Cristian Botau_)
 
(toc){width: 35em}*{text-align:center} *Conţinut:*
* 'Limbajul de asamblare: avantaje si dezavantaje in folosirea lui':introducere-in-asamblare#limbajul-de-asamblare
** 'Avantaje ale limbajului de asamblare':introducere-in-asamblare#avantaje-limbaj-asamblare
** 'Dezavantaje ale limbajului de asamblare':introducere-in-asamblare#dezavantaje-limbaj-asamblare
* 'Notiuni de baza ale limbajului de asamblare':introducere-in-asamblare#notiuni-de-baza
** 'Registrii':introducere-in-asamblare#registrii
*** 'Registrii de uz general':introducere-in-asamblare#registrii-de-uz-general
*** 'Registrii de segment si index':introducere-in-asamblare#registrii-de-segment-si-index
*** 'Registrii speciali':introducere-in-asamblare#registrii-speciali
*** 'Flagurile microprocesorului':introducere-in-asamblare#flagurile-microprocesorului
** 'Instructiunile de baza ale microprocesorului 8086':introducere-in-asamblare#instructiuni-de-baza-8086
*** 'Instructiunea MOV':introducere-in-asamblare#instructiune-MOV
*** 'Instructiuni aritmetice. Instructiunile ADD, SUB, INC, DEC':introducere-in-asamblare#instructiuni-aritmetice
*** 'Instructiuni pe biti. Instructiunile AND, TEST, OR, XOR, NOT':introducere-in-asamblare#instructiuni-pe-biti
*** 'Instructiunile PUSH/POP':introducere-in-asamblare#instructiuni-PUSH-POP
*** 'Instructiunea CMP':introducere-in-asamblare#instructiune-CMP
*** 'Jump-urile neconditionate (JMP) si jump-urile conditionate (JE, JNE, JB, JBE, JA, JAE, JZ, JNZ)':introducere-in-asamblare#jumpuri
*** 'Instructiunea LOOP':introducere-in-asamblare#instructiune-loop
*** 'Instructiunea INT':introducere-in-asamblare#instructiune-INT
* 'Transcrierea unor instructiuni C/PASCAL in asamblare':introducere-in-asamblare#instructiuni-CPascal-in-asamblare
** 'IF cond THEN instr':introducere-in-asamblare#if-then
** 'IF cond THEN instr1 ELSE instr2':introducere-in-asamblare#if-then-else
** 'DO { inst } WHILE':introducere-in-asamblare#do-while
* 'Program demonstrativ':introducere-in-asamblare#program-demonstrativ
* 'Linkuri':introducere-in-asamblare#linkuri
* 'Doua cuvinte conclusive...':introducere-in-asamblare#concluzie
 
Acest articol prezinta un limbaj mai putin folosit : limbajul de asamblare. Este folosit de cele mai multe ori pentru marirea vitezei de rulare a programelor. In prima sectiune sunt prezentate cateva avantaje / dezavantaje in folosirea acestui limbaj, iar ulterior sunt definite notiunile elementare ce trebuie stiute inainte de a trece la tratarea limbajului propriu-zis. De asemenea, sunt prezentate cateva intructiuni si transcrieri ale unor structuri din C/Pascal in limbaj de asamblare. In final sunt comparati timpii de executie intre varianta pascal si varianta assembler ale unei proceduri de {$Bubble Sort$}.
 
Limbajul de asamblare pe mediul x86 exista in doua tipuri de sintaxe :
 
* Intel
* AT&T
 
Sintaxa depinde de compilatorul folosit. De exemplu: Turbo Assembler (inclus in pachetul BC3.1) si Netwide Assembler (NASM) folosesc sintaxa Intel. GAS (folosit de GCC) foloseste sintaxa AT&T.
Nota: In GCC se poate folosi si sintaxa Intel, detalii 'aici':http://en.wikipedia.org/wiki/GNU_Assembler#Criticism .
 
 
O alta diferenta importanta este felul in care este interpretat cuvantul cheie _asm_ in diferite compilatoare C/C++.
De exemplu, aceeasi instructiune in mediul Borland, si GCC cu cele 2 sintaxe :
 
*Borland C 3.1 (sau TASM)*
== code(cpp) |asm { mov ax, bx };
==
 
*GCC (sintaxa AT&T)*
== code(cpp) |asm ("mov %bx, %ax");
==
 
*GCC (sintaxa Intel)*
== code(cpp) |asm (".intel_syntax noprefix\n\t"
     "mov ax, bx");
==
 
 
Exemplele prezentate in acest articol sunt scrise in sintaxa Intel.
 
h2(#limbajul-de-asamblare). Limbajul de asamblare : avantaje si dezavantaje in folosirea lui
 
Desi este unul dintre cele mai vechi limbaje de programare, limbajul de asamblare este incorporat si in cele mai noi compilatoare cum ar fi Visual C++ si Delphi. Codul de asamblare este foarte apropiat de codul executabil al programelor, asamblorul avand rolul de a codifica instructiunile din limbaj de asamblare in cod masina.
 
In prezent, limbajul de asamblare este din ce in ce mai putin folosit in scrierea programelor, deoarece compilatoarele de ultima generatie au functiile cele mai folosite de programatori deja scrise si optimizate in limbaj de asamblare (gen memmove, memset / fillchar, etc.) incluse in unit-uri si biblioteci. Bineinteles, daca se doreste atingerea unor timpi de executie foarte mici, folosirea limbajului de asamblare este preferabila (cand avem algoritmul de complexitate optima).
 
h3(#avantaje-limbaj-asamblare). Avantaje ale limbajului de asamblare
 
* Viteza foarte mare de executie a programelor (unele programe scrise si optimizate in asamblare au viteza de executie pana la 5-10 de ori mai mare decat cele scrise in C sau Pascal);
* Marimea codului executabil este foarte mica;
* Ajuta utilizatorul sa inteleaga mai bine cum functioneaza microprocesorul si sa scrie programe eficiente si in High Level Languages.
 
h3(#dezavantaje-limbaj-asamblare). Dezavantaje ale limbajului de asamblare
 
* nu este portabil; un program scris pentru calculatoare IBM PC nu va rula pe un Apple Macintosh (difera unele instructiuni);
* un program in asamblare este mai greu de scris si ia mai mult timp decat un program scris in Pascal sau C;
* un program scris in asamblare este mai greu de depanat;
 
Majoritatea compilatoarelor din prezent permit programatorului sa scrie anumite parti de cod sau subprograme in asamblare, astfel incat programatorul sa poata scrie in asamblare doar partea din program care se executa de cele mai multe ori, programul ruland astfel mult mai rapid. Sunt si compilatoare dedicate exclusiv limbajului de asamblare, cum ar fi TASM - Turbo Assembler (il gasiti in directorul BP/Bin), MASM - Microsoft Assembler, etc.
 
Pentru a scrie o secventa de limbaj de asamblare intr-un program $C$ se foloseste cuvantul rezervat $asm$ :
 
== code(cpp) |asm { instructiuni in asamblare };
==
 
iar in pascal :
 
== code(pas) |asm
    cod asamblare
end.
==
 
h2(#notiuni-de-baza). Notiuni de baza ale limbajului de asamblare
 
h3(#registrii). Registrii
 
Limbajul de asamblare este foarte apropiat de codul executabil, deci pentru ca programatorul sa fie capabil sa scrie codul in asamblare trebuie sa cunoasca modul in care functioneaza microprocesorul si modul in care acesta executa instructiunile.
 
Toate operatiile aritmetice/logice/acces la memorie se fac prin intermediul registrilor. Registrii microprocesorului sunt asemanatori variabilelor (pot fi initializati cu anumite valori, se pot efectua operatii asupra lor, pot fi accesati (cititi/modificati)).
Registrii microprocesorului sunt clasificati in mai multe categorii :
 
* registri de uz general : {$eax$}, {$ebx$}, {$ecx$}, {$edx$} si subregistrii lor;
* registri segment : {$cs$} (code segment), {$ds$} (data segment), {$es$} (extra segment), {$ss$} (stack segment) - sunt folositi pentru adresarea memoriei in cazul modelului de memorie segmentat
* registrii index : {$esi$}, $edi$ (source index, destination index);
* registri speciali : {$ebp$}, {$esp$}, {$ip$};
 
Registrii de uz general sunt folositi in executia operatiilor aritmetice si logice. Registrii de segment si cei index sunt folositi in adresarea memoriei, iar cei speciali sunt folositi de microprocesor si S.O. pentru rularea programelor.
 
h4(#registrii-de-uz-general). Registrii de uz general
 
Acestia sunt :
 
* $eax$ - numit si "accumulator" - acesta este cel mai folosit in executarea operatiilor aritmetice (adunare, scadere, inmultire, impartire);
* $ebx$ - numit si "base" - acesta registru este folosit pentru adresarea indirecta a memoriei;
* $ecx$ - numit si "count" - acest registru este folosit mai ales in cadrul "buclelor" (in instructiunile repetitive : for/ while do/ do while/ repeat until);
* $edx$ - numit si "data" - retine anumite variabile, si este folosit impreuna cu $eax$ in executarea operatiei "div", impartire intreaga (avand rolul de a retine restul impartirii).
 
Dupa cum ati observat, fiecare registru general are o functie specifica, insa oricare din ei pot fi folositi si la operatii aritmetice/logice, adresare memorie sau ca si variabile.
 
Registrii precedati de prefixul "{$e$}" sunt pe $32$ de biti, deci pot fi tratati ca si variabile {$unsigned long$}/{$signed long$} ({$doubleword$}). Acestia pot fi folositi si ca registri pe $16$ biti ({$word$}) sau pe $8$ biti ({$byte$}) prin intermediul "subregistrilor" : {$ax$}, {$bx$}, {$cx$}, $dx$ (reprezinta word-ul nesemnificativ = cel din dreapta)., {$ah$}, {$bh$}, {$ch$}, $dh$ (byte-ul cel mai semnificativ al {$ax$}, {$bx$}, {$cx$}, {$dx$}), {$al$}, {$bl$}, {$cl$}, {$dl$}(registrii pe 8 biti).
 
h4(#registrii-de-segment-si-index). Registrii de segment si index
 
Acesti registri sunt folositi la adresarea memoriei.
De exemplu
Avem variabila {$void *x;$}({$x : pointer$} in pascal);
pentru modelul de memorie segmentata, in adresarea memoriei adresate de $x$ se folosesc intructiunile (vezi "Intructiunile de baza ale unui microprocesor 8086") :
 
== code(cpp) |// es:[di] va accesa adresa pointata de x;
les di, x
// se copiaza in ax primul word
// de la adresa es:[di]
mov ax, es:[di]
// se copiaza in al cel de-al 3-lea byte
// de la adresa es:[di]
mov al, es:[di+2]
==
 
Parantezele drepte se refera la zona de memorie adresata de registru.
Exemplu :
 
"{$[esi]$}" se refera la valoarea aflata la adresa de memorie cu offset-ul esi, iar "{$esi$}" se refera la valoarea stocata in registrul esi;
 
daca $esi = 100$ atunci [{$esi$}] va reprezenta valoarea care se afla la adresa cu offset-ul {$100$}, iar esi va reprezenta valoarea {$100$}.
 
Va sfatuiesc sa NU incercati sa modificati urmatorii registri : $cs$ (care contine segmentul la care se afla incarcat codul executabilului, $ss$ contine segmentul de memorie in care se afla stiva sistemului).
 
h4(#registrii-speciali). Registrii speciali
 
Sunt folositi foarte rar in scrierea efectiva a programelor, deci nu veti accesa prea des acesti registri.De exemplu, $ip$ (instruction pointer) care retine adresa (de fapt offset-ul) la care se afla instructiunea care urmeaza sa fie executata.
 
h4(#flagurile-microprocesorului). Flagurile microprocesorului
 
Flag-urile microprocesorului sunt asemanatoare unor variabile boolene. Cu ajutorul lor putem afla rezultatul unei comparatii sau daca o operatie a produs "overflow", etc. Aceste flag-uri nu pot fi accesate direct ca si registrii, ci testarea daca un flag este activat se face prin intermediul "jump"-urilor conditionate.(vezi instructiunea {$cmp$}).
 
Exemplu :
Fie {$ax = 1$}. Decrementarea lui $ax$ cu $1$ va duce la activarea flagului numit Zero-Flag.(rezultatul operatiei a fost {$0$}).
Fie {$ax = 65535$} ({$0xFFFF$}). Incrementarea lui $ax$ cu $2$ va duce la setarea flagului numit Overflow-Flag (rezultatul operatiei nu "incape" in $16$ biti);
 
h3(#instructiuni-de-baza-8086). Instructiunile de baza ale microprocesorului 8086
 
Sunt folosite urmatoarele prescurtari :
 
* {$reg8$}/ {$reg16$}/ {$reg32$} - se refera la registri pe {$8$}/{$16$}/{$32$} biti;
* {$imm8$}/ {$imm16$}/ {$imm32$} - se refera la valori imediate (constante);
* {$mem8$}/ {$mem16$}/ {$mem32$} - zona memorie de memorie sau variabile pe {$8$}/{$16$}/{$32$} biti;
 
h4(#instructiune-MOV). Instructiunea MOV
 
_Sintaxa_:
 
* $mov dest, source$ // echivalenta cu dest = source
 
_Variante_:
 
* $mov reg8/16/32, mem8/16/32$
* $mov reg8/16/32, reg8/16/32$
* $mov mem8/16/32, reg8/16/32$
* $mov reg8/16/32, imm8/16/32$
* $mov mem8/16/32, imm8/16/32$
 
_Descriere_:
 
Instructiunea copiaza o valoare dintr-o locatie in alta locatie. Aceasta locatie poate fi zona de memorie, variabila, registru. De retinut este ca nu exista variante ale instructiunii care copiaza direct dintr-o zona de memorie in alta.
 
_Flaguri afectate_:
 
Instructiunea mov nu modifica nici un flag.
 
_Restrictii_:
 
Ambii operanzi trebuie sa aiba aceeasi marime. De exemplu pentru ultima varianta a instructiunii mov trebuie specificata marimea zonei de memorie. Instructiunea "mov [bx], 0" nu este corecta deoarece compilatorul nu stie ce vrea sa faca instructiunea: sa copieze valoarea 0 in byte-ul, in word-ul sau in doubleword-ul de la adresa bx. Astfel variantele corecte sunt:
 
== code(cpp) |mov byte ptr [bx], 0
mov word ptr [bx], 0
mov dword ptr [bx], 0
==
 
_Exemplu_:
 
== code(cpp) |mov ax, 3 // ax = 3
mov bx, ax // bx = ax
==
 
h4(#instructiuni-aritmetice). Instructiuni aritmetice. Instructiunile ADD, SUB, INC, DEC
 
_Sintaxa_:
 
* $add dest, source$ // echivalenta cu dest+=source
* $sub dest, source$ // echivalenta cu dest-=source
* $inc dest$ // echivalenta cu dest++
* $dec dest$ // echivalenta cu dest--
 
_Variante_:
 
* $add(sub) reg8/16/32, mem8/16/32$
* $add(sub) reg8/16/32, reg8/16/32$
* $add(sub) mem8/16/32, reg8/16/32$
* $add(sub) reg8/16/32, imm8/16/32$
* $add(sub) mem8/16/32, imm8/16/32$
 
_Descriere_:
 
Instructiunea "{$add$}" este folosita pentru a aduna doua valori, "{$sub$}" pentru a scadea o valoare din alta, "{$inc$}" - incrementarea unei variabile, "{$dec$}" - decrementarea unei variabile (registru/memorie).
 
_Flaguri afectate_:
 
* $carry flag$ - pentru "signed overflow"
* $overflow flag$ - pentru overflow
* $sign flag$ - activat daca rezultatul este negativ
* $zero flag$ - activat daca rezultatul operatiei a fost 0
* $parity flag$ - este setat in functie de paritatea rezultatului
 
_Restrictii_:
 
Destinatia trebuie sa aiba aceeasi marime ca si sursa.
 
_Exemplu_:
 
== code(cpp) |mov ax, 3 // ax = 3
add bx, ax // bx = ax+3
==
 
h4(#instructiuni-pe-biti). Instructiuni pe biti. Instructiunile AND, TEST, OR, XOR, NOT
 
_Sintaxa_:
 
* $and dest, source$ // echivalenta cu dest&=source
* $test dest, source$ // dest & source
* $or dest, source$ // echivalenta cu dest|=source
* $xor dest, source$ // echivalenta cu dest^=source
* $not dest$ // ~dest
 
_Variante_:
 
Pentru "{$and$}", "{$or$}", "{$test$}", "{$xor$}" sunt ca si cele de la "{$add$}". Pentru "{$not$}" avem doua variante :
 
* $not reg8/16/32$
* $not mem$
 
_Descriere_:
 
Executa operatiile pe biti corespunzatoare. Instructiunea test (non-destructive and) este asemanatoare lui and, dar nu afecteaza rezultatul ci doar flagurile.
 
_Flaguri afectate_:
 
Instructiunea "{$not$}" nu afecteaza nici un flag.
Flagurile afectate de celalalte instructiuni sunt :
 
* {$carry$}, $overflow$ - le dezactiveaza
* $sign flag$ - este copiat bitul cel mai semnificativ din rezultat (semnul rezultatului, de fapt)
* $zero flag$ - activat daca rezultatul operatiei a fost 0
* $parity flag$ - este setat in functie de paritatea rezultatului
 
_Restrictii_:
 
Destinatia trebuie sa aiba aceeasi marime ca si sursa.
Instructinile $OR/XOR/INC/DEC$ modifica flag-urile microprocesorului: {$Carry$}(numai {$ADD/SUB$}), {$Overflow$}, {$Zero$}, {$Parity$}.
 
_Exemplu_:
 
== code(cpp) |mov ax, 5 // ax = 3
test ax, 1 // daca (ax & 1)
jz @skip // daca ax este nu este impar sari la @skip
// executa instructiuni
@skip
and bx, FFFEh // bx = bx & 0xFFFE (se sterge bitul cel mai nesemnificativ)
==
 
h4(#instructiuni-PUSH-POP). Instructiunile PUSH/POP
 
_Descriere_:
 
Aceste instructiuni sunt folosite pentru accesarea stivei sistemului. Instructiunea PUSH pune pe stiva sistemului o valoare, iar instructiunea POP extrage valoarea din varful stivei.
 
_Sintaxa_:
 
* $push val$
* $pop val$
 
_Variante_:
 
* $push reg8/16/32$
* $pop reg8/16/32$
 
_Exemplu_:
 
== code(cpp) |@lp:
push cx // salvam valoarea cx
... // prelucreaza datele, se poate modifica cx
pop cx // scoate de pe stiva valoarea cx
dec cx
jnz @lp // daca (cx != 0) atunci sari inapoi la @lp
==
 
h4(#instructiune-CMP). Instructiunea CMP
 
_Descriere_:
 
Compara doua valori si seteaza flag-urile microprocesorului cu rezultatele compararii. De cele mai multe ori este folosita in combinatie cu jump-urile (vezi mai jos).
 
_Sintaxa_:
 
* $cmp val1, val2$
 
_Variante_:
 
* $cmp reg8/16/32, reg8/16/32$
* $cmp reg8/16/32, imm8/16/32$
* $cmp mem8/16/32, reg8/16/32$
* $cmp mem8/16/32, imm8/16/32$
 
h4(#jumpuri). Jump-urile neconditionate (JMP) si jump-urile conditionate (JE, JNE, JB, JBE, JA, JAE, JZ, JNZ)
 
Aceste instructiuni sunt folosite pentru generarea instructiunilor If /while do /do while /repeat /for.Sunt asemanatoare instructiunii goto din C/Pascal.
 
_Sintaxa_:
 
$instructiune label$
 
* $JE$ * "sare" cu executia programului la label daca flag-ul equality este setat;
* $JNE$ * "sare" cu executia programului la label daca flag-ul equality nu este setat;
* $JB$ * "sare" cu executia programului la label daca flag-ul below este setat;
* $JBE$ * "sare" cu executia programului la label daca flag-ul below sau flagul equality este setat;
* $JA$ * "sare" cu executia programului la label daca flag-ul below nu este setat;
* $JAE$ * "sare" cu executia programului la label daca flag-ul equality este setat sau flag-ul below nu este setat;
* $JZ$ * "sare" cu executia programului la label daca flag-ul zero este setat;
* $JNZ$ * "sare" cu executia programului la label daca flag-ul zero nu este setat;
* $JMP$ * "sare" cu executia programului la label;
 
_Exemplu_:
 
== code(cpp) |mov ax, 1
@loop :
    instr1
    instr2
    *
    inc ax
    // compara ax cu valoarea 10 si seteaza flagurile
    cmp ax, 10
    // daca ax != 10 sari inapoi la @loop
 
jne @loop
 
== code(cpp) |instr1
jmp @label1
    instr2
    instr3
@label1 :
==
 
In ultimul exemplu instr2 si instr3 nu vor fi executate deoarece inaintea lor se afla instructiunea jump care face ca programul sa sara cu executia la instructiunea imediat urmatoare etichetei "label1".
 
h4(#instructiune-loop). Instructiunea LOOP
 
Instructiunea "loop" este folosita de obicei in transcrierea for-urilor si a buclelor.
 
_Sintaxa_:
 
* $loop label$
 
_Descriere_:
 
Echivalenta cu :
 
== code(cpp) |dec ecx (sau dec cx)
jz label
==
 
h4(#instructiune-INT). Instructiunea INT
 
_Descriere_:
 
Instructiunea INT apealeaza o intrerupere.
 
_Sintaxa_:
 
* $INT nr_intrerupere$ (un numar cuprins intre 0 si 255);
 
Intreruperile opresc programul din rulare, executa un anumit cod si apoi permit continuarea rularii programului. Cu ajutorul apelului intreruperilor aplicatia poate sa comunice cu sistemul de operare, si cu unele componente hardware ale calculatorului (mouse-ul, tastatura, imprimanta, etc). Fiecare intrerupere are asociat un numar.
 
_Exemple_:
 
* $0x9$ : intreruperea pentru tastatura;
* $0x10$ : intreruperea care ofera serviciile BIOS pentru accesul la placa video;
* $0x21$ : intreruperea sistemului de operare DOS;
* $0x33$ : intreruperea pentru mouse;
 
In DOS pentru initializarea mouse-ului se va apela functia numarul $0$ a intreruperii $0x33$ :
 
== code(cpp) |asm
{
    mov ax, 0
    // seteaza registrul ax cu valoarea 0 pentru ca
    // driverul ce gestioneaza intreruperea 0x33
    //sa stie ca trebuie sa initializeze mouse-ul
    int 0x33
}
==
 
Pentru afisarea mouse-ului :
 
== code(cpp) |asm
{
    mov ax, 1
    int 0x33
}
==
 
Pentru ascunderea cursorului de la mouse :
 
== code(cpp) |asm
{
    mov ax, 2
    int 0x33;
}
==
 
h2(#instructiuni-CPascal-in-asamblare). Transcrierea unor instructiuni C/PASCAL in asamblare
 
h3(#if-then). IF cond THEN instr
 
Exemplu :
 
== code(cpp) |if (a==5) b=a; // a, b : integer
==
 
== code(cpp) |mov ax, a
cmp ax, 5
// ne intrebam daca !cond
jnz @skip_if
mov b, ax
@skip_if :
==
 
h3(#if-then-else). IF cond THEN instr1 ELSE instr2
 
Exemplu:
 
== code(cpp) |if (a==b) a-=b; else b-=a;
==
 
Transcrierea in asamblare :
 
== code(cpp) |mov ax, a
mov bx, b
// compara ax cu bx si seteaza flag-urile
cmp ax, bx
je @if_then
// if (ax == bx) sari la @if_then
sub b, ax
jmp @end_if :
@if_then :
sub a, bx
@end_if :
==
 
 
h3(#do-while). DO { inst } WHILE
 
Exemplu :
 
== code(cpp) |int i, a, n;
i = 0;
a = 0;
do
{
    i++;
    a+=i;
} while (i<=n);
==
 
Codul in asamblare :
 
== code(cpp) |// cx = 0, instructiunea xor cx, cx este
// mai rapida decat mov cx, 0
xor cx, cx
xor ax, ax
mov bx, n
@while :
    inc cx
    add ax, cx
    cmp cx, bx
    jne @while
mov a, ax
mov I, cx
==
 
Sau folosind instructiunea loop (pornim invers) :
 
== code(cpp) |mov cx, n
@while:
    add ax, cx
loop @while
==
 
In codul de mai sus (prima varianta) puteti observa cateva mici optimizari : retinerea variabilelor in registri (accesul la registri este mult mai rapid decat la memorie), inlocuirea instructiunii {$mov reg, 0$ cu {$xor reg, reg$}. Varianta "xor" este mai rapida (desi $mov$ si $xor$ "teoretic" au tot atatea ceasuri de procesor) deoarece instructiunea $mov reg, 0$ transcrisa in cod masina este mai "lunga" (are cu $2-4$ bytes mai mult decat {$xor reg, reg$}). Aceeasi regula se aplica si in cazul "{$or reg, reg$}" in testarea daca valoarea unui registru este egala cu {$0$}, intrucat instructiunea "{$or$}" modifica flagurile procesorului ({$ZeroFlag$}). O alta optimizare este folosirea registrului $ax$ pentru calcule (in majoritatea cazurilor instructiunile care folosesc registrul $(e)ax$ sunt mai rapide).
 
h2(#program-demonstrativ). Program demonstrativ
 
Exemplu de functie in realizata in C/Asamblare (compilator folosit BC++ 3.1):
 
== code(cpp) |// BSort.CPP
#include <stdio.h>
#include <conio.h>
 
int a[20], n;
 
void bsort()
{
	asm {
		mov di,seg a
		mov es,di
		mov di,offset a
		// es:[di] inceputul vectorului
		// es - adresa segment
		// di - adresa offset
	};
 
do_while_not_ok :
	asm {
		// ok = 1;
		mov dx, 1
		// cx = n;
		mov cx, n
		// cx = n-1
 
		dec cx
		// ax = a[0]
		mov ax, es:[di]
		// punem di pe stiva
		push di
	}
inner_for :
	asm {
		// bx = a[i-1]
		mov bx, ax
		// ne mutam pe noua pozitie in vector
		add di, 2
		// ax = a[i]
		mov ax, es:[di]
		// comparam ax cu bx (a[i-1] cu a[i])
		cmp bx, ax
		// if (a[i-1] <= a[i]) don't swap
		jbe if_not
		// interschimbam a[i-1] cu a[i]
		xchg bx, ax
		// le scriem in memorie
		mov es:[di], ax
		mov es:[di-2], bx
		// ok = 0
		xor dx, dx
	}
if_not :
	asm {
		// cx--;
		dec cx
		jnz inner_for
		// daca cx != 0 atunci continuam for-ul
		pop di
		// compara dx cu 0
		// (mai rapid decat cmp dx, 0)
		or dx, dx
		// if (ok) executam inca odata while-ul
		jz do_while_not_ok
	}
}
 
void citire()
{
	printf("Numarul de elemente : ");
	scanf("%d", &n);
	for (int i=0; i<n; i++)
	{
		printf("a[%d] = ", i);
		scanf("%d", &a[i]);
	}
}
 
void afis()
{
	for (int i=0; i<n; i++)
		printf("%d ", a[i]);
}
 
int main()
{
	citire();
	bsort();
	afis();
	return 0;
}
==
 
Varianta Pascal + Asm (compilata cu BP 7.0):
 
== code(pas) |program b_sort;
 
const   max_N = 1000;
        n_tests = 100;
 
var N : Integer;
    a, b : array [1..max_N] of Integer;
 
procedure citire;
var f : Text;
    i : Integer;
begin
    Assign(f, 'bsort.in'); Reset(f);
    readln(f, N);
    for i := 1 to N do
        read(f, b[i]);
    Close(f)
end;
 
procedure flip;
var i : Integer;
begin
    for i:=1 to N do
        a[i] := b[i];
end;
 
var p : pointer;
 
procedure bsort; assembler;
asm
{ les <=> es = seg(p), di = offset(p) }
    les di, p
    @do_while_not_ok :
        mov dx, 1
        mov cx, N
        dec cx
        mov ax, es:[di]
        push di
        @inner_for :
            mov bx, ax
            add di, 2
            mov ax, es:[di]
            cmp bx, ax
            jbe @if_not
            xchg bx, ax
            mov es:[di], ax
            mov es:[di-2], bx
            xor dx, dx
            @if_not :
                dec cx
                jnz @inner_for
        pop di
        or dx, dx
    jz @do_while_not_ok
end;
 
procedure bsort2;
var ok : Boolean;
    i, aux : Integer;
begin
    repeat
        ok := true;
        for i := 1 to n-1 do
            if (a[i] > a[i+1]) then
            begin
                aux := a[i];
                a[i] := a[i+1];
                a[i+1] := aux;
                ok := false;
            end;
    until ok;
end;
 
var i : Integer;
    starttick, endtick : LongInt;
    time : Longint absolute $0000:$046C;
 
{ timer pentru masurarea timpului }
const tickspersecond = 18.206;
 
procedure starttimer;
begin
    starttick := time;
end;
 
function elapsedtime : real;
begin
    endtick := time;
    elapsedtime := (endtick - starttick)*
        (1 / tickspersecond);
end;
 
begin
    citire;
    p := @a;
    starttimer;
    for i:=1 to n_tests do
    begin
        flip;
        bsort;
    end;
    writeln(n_tests, ' rulari ale bsort in asamblare
        au rulat in : ', elapsedtime : 6:3, ' secunde');
    starttimer;
    for i:=1 to n_tests do
    begin
        flip;
        bsort2;
    end;
    writeln(n_tests, ' rulari ale bsort obisnuit
            au rulat in : ',elapsedtime : 6:3, ' secunde');
end.
==
 
Output program pascal (pe pc-ul meu: Duron $750$ Mhz):
 
* $100$ rulari ale bsort in asamblare au rulat in : $1.263$ secunde
* $100$ rulari ale bsort obisnuit au rulat in : $4.614$ secunde
 
De precizat este ca nu am optimizat programul aproape deloc (am transcris codul din pascal in asm). Cu optimizari "hardcore" se pot obtine timpi mult mai buni. Diferenta dintre varianta asm si varianta high level language este mai mica pe compilatoarele mai noi, cum ar fi GCC sau FreePascal deoarece acestea stiu genera un cod mai optimizat. Totusi, nici un compilator nu poate bate creierul uman.
 
h2(#linkuri). Linkuri
 
* '_The Art of Assembly Language_':http://www.scribd.com/doc/6498072/Art-of-Assembly. O carte detaliata si foarte buna atat pentru incepatori cat si pentru avansati.
 
* '_How to optimize for the Pentium processor_':http://www.goof.com/pcg/doc/pentopt.txt. Un tutorial bun pentru optimizarea programelor in asamblare pentru procesoarele din familia Pentium. Tutorialul este pentru avansati.
 
* '_Assembly Tutorials_':http://www.techtutorials.info/assembly.html. Mai multe tutoriale despre assembly language.
 
h2(#concluzie). Doua cuvinte conclusive...
 
Acest articol trateaza "bazele" programarii in asamblare, dupa cum ii zice numele este doar o introducere. Pentru a sti sa programezi in _asm_, trebuie invatate mult mai multe lucruri. Scopul articolului este de a starni interesul cititorilor si de a le oferi o imagine asupra acestui limbaj. Cei ce doresc sa invete mai multe despre _assembly language_ sunt invitati sa citeasca cartile / tutorialele din sectiunea 'linkuri':introducere-in-asamblare#linkuri.
 

Nu exista diferente intre securitate.

Diferente intre topic forum:

 
3676