<docere>http://www.docere.ro/ |

Zona stivei şi transferul parametrilor
Zona stivei este utilizată ca zonă de date în două situaţii importante:
— pentru comunicarea parametrilor către un subprogram;
— drept zonă a datelor locale unui subprogram.
De asemenea, zona stivă serveşte şi pentru salvarea conţinutului regiştrilor, în situaţia când execuţia programului urmează să fie întreruptă prin activarea unei subrutine care şi ea, are nevoie să utilizeze regiştrii respectivi.
De obicei, porţiunea din zona stivă a programului care cuprinde parametrii transmişi de către programul apelant şi variabilele locale considerate în subprogramul respectiv este denumită cadrul stivă al subprogramului; de fapt, cadrul stivă asociat unui subprogram include totdeauna încă două elemente (cum vom vedea mai încolo).
Să presupunem că avem un subprogram P1(x, y) unde x şi y sunt parametrii care urmează să fie primiţi de la programul care apelează P1; în vederea apelării lui P1, programul apelant va depune în zona stivei valorile respective (folosind PUSH) şi apoi va folosi CALL P1. Deci la activarea subprogramului P1, zona stivei are următoarea descriere:
SP_vechi —> ---- (partea superioară a stivei, până la momentul iniţierii apelului la P1)
—> primul parametru transmis lui P1 (sau x, sau y)
—> al doilea parametru transmis (y, respectiv x)
SP_nou —> adresa de revenire în programul apelant (după execuţia lui P1)
P1 ar putea accesa valorile transmise lui drept parametri, adăugând un deplasament la valoarea curentă SP_nou a registrului SP: al doilea parametru este la adresa SP + 2, iar primul la adresa SP + 4; parametrul este într-adevăr indicat de [SP + deplasament], dar valoarea respectivă trebuie nu atât indicată, cât preluată ca atare de către subprogram (adică trebuie să se dispună de o instrucţiune "mov DEST, [REG]" prin care să se transfere la DEST valoarea a cărei adresă o conţine REG; nu pentru toţi REG, este cablată o astfel de instrucţiune - cel puţin nu pentru oricare CPU).
Desigur că revenirea din P1 în programul apelant va trebui să însemne nu numai recuperarea adresei de revenire indicată de SP_nou, dar şi eliberarea imediată a locului ocupat în stivă de valorile transmise, devenite inutile. Această curăţare a stivei se va putea face adăugând la SP_nou deplasamentul necesar pentru a ajunge la nivelul SP_vechi.
CPU dispune de instrucţiuni ADD SP, n şi SUB SP, n prin care se avansează SP cu n octeţi, într-un sens sau în celălalt; ADD SP, n "elimină" din stivă n octeţi, în sensul că poziţionează SP "deasupra" lor, iar SUB SP, n rezervă (sau alocă) în stivă n octeţi, poziţionând SP "dedesubtul" lor (desigur, ADD şi SUB sunt mnemonicele generale pentru adunare şi scădere; dar cu referire la SP, ele capătă semnificaţia precizată - de a "elimina" din stivă şi de a "rezerva" loc în stivă; eliminarea celor n octeţi din stivă înseamnă doar că ei nu vor mai fi sub controlul programului respectiv, putând fi ulterior suprascrişi de un alt program care ar căpăta temporar controlul).
Dacă P1 prevede nişte variabile locale acestea vor putea fi alocate "dedesubtul" lui SP_nou: în cadrul lui P1 se va coborî SP cu numărul de octeţi necesar variabilelor locale (SUB SP, n), urmând ca în zona rezervată astfel să se păstreze valorile curente ale acelor variabile; apoi, imediat înainte de a reveni în programul apelant, zona variabilelor locale va fi "eliminată" prin ADD SP, n (încât SP revine la nivelul SP_nou care indică adresa de revenire).
Dar gestionarea stivei în acest mod (prin simpla manevrare a valorii SP curente) se va complica în situaţia apelurilor imbricate. Dacă P1 prevede variabile locale, atunci acestea trebuie să fie accesibile oricărui subprogram P2 apelat din P1, ca şi oricărui subprogram P3 apelat din P2. Ori din cadrul lui P3 nu se va mai putea calcula deplasamentul necesar pentru a indica variabilele locale din cadrul stivă corespunzător lui P1 (decât cunoscând, la nivelul P3, mai multe informaţii care de fapt sunt străine de activitatea specifică lui P3: dimensiunea zonelor de parametri şi de variabile locale ale "strămoşilor" lui P3).
Prin urmare, se impune ca la nivelul subprogramului apelat (în cadrul stivă asociat acestuia) să fie înregistrată o informaţie despre nivelul ierarhic superior, adică despre nivelul la care se află în zona stivei, cadrul stivă al programului apelant. Realizarea cea mai simplă şi cea mai sigură a acestei idei constă în a considera împreună cu SP, încă un registru - să-l numim REG (în realitate este vorba de registrul Base Pointer). Acesta va memora nivelul în zona stivei, al cadrului asociat subprogramului (sau chiar "programului principal" - la urma urmei şi acesta este "apelat", anume din mediul de operare - ceea ce ar reveni la o iniţializare REG = 0).
Pe parcursul execuţiei unui subprogram, REG păstrează adresa cadrului stivă al acestui subprogram (permiţând şi accesarea comodă a valorilor înscrise în cadrul stivă respectiv). La activarea unui subprogram, acesta va adăuga în cadrul stivă propriu valoarea existentă în REG (adică adresa cadrului stivă a programului care l-a apelat) şi va înregistra în REG adresa propriului cadru stivă. În finalul execuţiei subprogramului, înainte de a permite revenirea în programul apelant, subprogramul va trebui să reconstituie în REG valoarea salvată la debut, caracteristică programului în care se revine.
Pentru a permite (pe lângă referirea de cadre stivă) şi obţinerea uşoară a valorilor existente în cadrul stivă respectiv, REG trebuie înzestrat cu sarcina realizării unor instrucţiuni de tip "mov DEST, [REG]" - prin care să se transfere la DEST valoarea de la adresa conţinută de REG. Registrul SP de exemplu, este scutit de această sarcină: nu este cablată o instrucţiune "mov DEST, [SP]" (sarcina aceasta trebuind să fie efectuată în două părţi, de exemplu prin mov BX, SP şi apoi mov DEST, [BX]).
Drept REG, s-a adăugat un registru special, denumit BP Base Pointer. Rolul lui REG ar putea fi preluat de asemenea, de registrul BX, fiind cablate instrucţiunile mov DEST, [BX] şi mov DEST, [BP] (pe ultimele generaţii de microprocesoare Intel sunt cablate instrucţiuni mov DEST, [REG] şi pentru alţi regiştri generali - AX, CX, DX).
Pentru o exemplificare, să considerăm acest schelet de program Pascal:
program P;
var a, b: integer;
procedure P1(x, y: integer);
var i, j: integer;
begin --- end;
begin
---
P1(100, 567);
a := 1;
---
end.
Apelarea procedurii P1 se realizează prin următoarea secvenţă:
push 100 ; transmite valoarea primului parametru din lista de parametri push 567 ; transmite valoarea pentru al doilea parametru call P1 ; memorează adresa de revenire (la "a:=1") şi predă controlul lui P1
Parametrii pot fi transmişi de la stânga spre dreapta - întâi 100, apoi 567 - adică folosind convenţia Pascal, sau de la dreapta spre stânga în convenţia C.
Ca urmare a execuţiei secvenţei de mai sus, se va constitui în segmentul de memorie cu adresa de bază dată de SS, cadrul stivă asociat procedurii P1:
| adresă | conţinut | |
|---|---|---|
| ---- | cadrul stivă al programului apelant, indicat | |
| SP = s0 = SP_vechi | ---- | de valoarea curentă existentă în REG (BP) |
| SP = s1 = s0 - 2 | 100 | valoarea pentru x |
| SP = s2 = s1 - 2 | 567 | valoarea pentru y |
| SP = s3 = s2 - 2 | adresa instrucţiunii "a:=1" | adresa de revenire |
|
P1 completează imediat acest cadru, cu adresa REG specifică cadrului stivă al programului apelant: | ||
| SP = s4 = s3 - 2 | REG (în speţă BP) | după push BP |
|
şi apoi fixează în REG adresa propriului cadru-stivă: | ||
| mov REG, s4 | mov BP, SP | |
De-acum încolo, P1 va putea referi propriul cadru stivă folosin REG (în speţă, BP): la nivelul [REG + 2] se află adresa de revenire, la nivelul [REG + 4] - valoarea lui y, etc. Valoarea transmisă pentru x se va putea obtine de exemplu, cu mov AX, [BP + 6].
Observăm în textul=sursă că procedura P1 are variabilele locale i, j:integer;. P1 realizează alocarea necesară pentru acestea prin sub SP, 4 determinând extinderea cadrului stiva pentru a păstra valorile variabilelor locale i, j:
s5 = s4 - 2 rezervat pentru valoarea curentă a lui i SP = s6 = s5 - 2 rezervat pentru valoarea curentă a lui j
Accesarea variabilelor locale i, j se va realiza folosind de asemenea REG: mov AX, [BP - 2] va transfera valoarea curentă i în AX, iar mov [BP - 4], 1234 va realiza atribuirea j := 1234.
La încheiere, înainte de a permite revenirea în programul apelant, P1 trebuie să reconstituie în REG valoarea proprie programului apelant (secvenţa "epilog"):
mov SP, BP ;SP revine la nivelul s4, "eliberând" implicit zona variabilelor locale pop BP ;REG reia valoarea înscrisă la s4, iar SP ajunge la nivelul s3
La nivelul s3 se află adresa de revenire în programul apelant şi RET ar determina această revenire (la adresa instrucţiunii "a := 1"). Dar în convenţia Pascal "curăţirea" stivei (de parametrii x, y deveniţi inutili) revine tot subprogramului şi anume se foloseşte instrucţiunea RET 4 - prin care se pune IP = adresa de revenire, dar simultan se incrementează SP cu 4, revenind deci la SP = s0 şi "eliminând" astfel parametrii x, y (am presupus că integer ocupă 2 octeţi).
În schimb, convenţia C prevede curăţirea stivei de parametrii transmişi nu de către subprogramul apelat, ci de către apelant (care oricum, "ştie" câţi şi ce fel de parametri a transmis); deci P1 şi-ar încheia activitatea cu RET, redând controlul către P, urmând ca acesta să elimine parametrii fie cu add SP, 4 fie cu pop CX ("descarcă" primul parametru) şi pop CX ("descarcă" şi al doilea argument).
➀ Codul unui program care conţine multe apeluri de subprograme va fi mai scurt dacă este generat pe baza convenţiei Pascal şi nu pe baza convenţiei C (întrucât secvenţa de descărcare a stivei este conţinută de subprogramul apelat în cazul Pascal, pe când în celălalt caz ea este amplasată în programul apelant, după instrucţiunea CALL de apelare a subprogramului).
➁ Dacă P1 ar apela la rândul său, P2(a), atunci compilatorul ar constitui cadrul stivă corespunzător lui P2 astfel:
s7 = s6 - 2 parametrul a s8 = s7 - 2 valoarea REG (BP) s9 = s8 - 2 adresa de revenire din P2 în P1
adică ar adăuga drept parametru şi valoarea REG proprie cadrului stivă al lui P1; aşa, se va permite lui P2 accesarea variabilelor locale ale lui P1. După "prologul" din P2: push BP; mov BP, SP care determină completarea cadrului stivă cu:
(REG=BP = ) SP = s10 = s9 - 2: [REG] (valoarea din BP)
valoarea variabilei locale x din P1 se va putea accesa astfel:
mov BX, [BP + 4] (BX = valoarea REG din P1) mov AX, [BX] (AX = valoarea lui x din P1)
➂ Dacă subprogramul apelat P1 ar face parte dintr-un alt segment de cod decât cel care corespunde programului apelant (apelare far), atunci apelarea lui P1 necesită şi modificarea registrului CS, astfel că valoarea CS a programului apelant trebuie şi ea salvată:
push primul parametru push al doilea parametru push CS call P1
Aceasta determina creşterea cu 2 a cadrului stivă al lui P1, deci adresarea prin BP va fi cu +2 faţă de cazul near prezentat mai sus (BP + 2 împreună cu BP + 4 indică adresa CS:IP de revenire; BP + 6 referă al doilea parametru şi [BP + 8] conţine valoarea primului parametru).
➃ În cele de mai sus, am considerat implicit ca adresa de revenire (depusă de apelant în stivă, înainte de a şi pasa controlul executantului apelat) ar avea dimensiunea de 2 octeţi (în cazul near), sau de 4 octeţi (în cazul far). Poate exista însă şi un alt caz: dacă segmentul de cod are atributul USE32 (deci el este referit cu un deplasament de tip dword), atunci se foloseşte registrul extins EIP drept "instruction pointer" şi drept urmare, adresele de revenire (înapoi în segmentul de 32 biţi) au ele însele, câte 4 octeţi; deci în acest caz (posibil de la I80386 încoace), adresa de revenire are 4 octeţi în cazul near şi 4 + 2 = 6 octeţi în cazul far.
ORAR orarul şcolii
SitSco situaţie şcolară
ŞAH prin corespondenţă
doChess a Javascript chess engine
doPGN a Javascript PGN-browser
Cal++ ambiţiile Calului
aşaAzis momente lingvistice
Comentarii
—cum ar trebui calculată Media şcolară?
completely rethink the browser:
Google chrome