Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cpl:labs:06 [2015/11/09 23:37]
laura.vasilescu [Exerciții]
cpl:labs:06 [2016/11/08 00:02] (current)
bogdan.nitulescu [Exerciții]
Line 1: Line 1:
-====== 06. Reprezentarea datelor ​======+====== 06. Code generation. Structure of data and objects in memory ​======
  
 În urma compilării și linkării unui program rezultă un fişier binar care, indiferent de formatul lui, este reprezentarea programului în memoria procesorului pentru care a fost compilat. Aceasta reprezentare conține în mod uzual mai multe secțiuni implicite, secțiuni care în funcție de format au diverse denumiri, dar scopul lor este asemănător. În urma compilării și linkării unui program rezultă un fişier binar care, indiferent de formatul lui, este reprezentarea programului în memoria procesorului pentru care a fost compilat. Aceasta reprezentare conține în mod uzual mai multe secțiuni implicite, secțiuni care în funcție de format au diverse denumiri, dar scopul lor este asemănător.
Line 75: Line 75:
 Pointerii sunt nişte variabile ce conțin o adresă din memorie. Aspecte care poate nu sunt evidente pentru toată lumea: Pointerii sunt nişte variabile ce conțin o adresă din memorie. Aspecte care poate nu sunt evidente pentru toată lumea:
   * Dimensiunea unui pointer este dimensiunea unui cuvant din arhitectura pentru care s-a compilat (tipul de date ''​long''​ în C). Din punct de vedere al standardului C, nu este corect sa se presupuna ca un un pointer are aceeași dimensiune cu un număr ''​long''​.   * Dimensiunea unui pointer este dimensiunea unui cuvant din arhitectura pentru care s-a compilat (tipul de date ''​long''​ în C). Din punct de vedere al standardului C, nu este corect sa se presupuna ca un un pointer are aceeași dimensiune cu un număr ''​long''​.
-  * "Tip *a" **NU** este echivalent cu "Tip a[ ]". Majoritatea cred că a[5] este echivalent cu *(a+5) și crede că a declara o variabilă ca "​pointer"​ este echivalent cu a o declara "​array"​. După cum s-a explicat mai sus - array-ul e un șir continuu de elemente de tipul "​Tip",​ pe când pointer-ul e o adresa la un element de tipul "​Tip"​. ​Concret, dacă avem variabilele globale a și b declarate ca "int* a;", respectiv "int b[];":​ +  * "Tip *a" **NU** este echivalent cu "Tip a[ ]". Majoritatea cred că a[5] este echivalent cu *(a+5) și crede că a declara o variabilă ca "​pointer"​ este echivalent cu a o declara "​array"​. După cum s-a explicat mai sus - array-ul e un șir continuu de elemente de tipul "​Tip",​ pe când pointer-ul e o adresa la un element de tipul "​Tip"​. ​Rezultatul operatorului ''​sizeof''​ diferă.
-    * pentru a calcula a[5] se află **valoarea** variabilei a, se adună "5*sizeof(int)" și se întoarce valoarea aflată la această adresă +
-    * pentru a calcula b[5] se afla **adresa** variabilei b, se adună "​5*sizeof(int)"​ se întoarce valoarea aflată la această adresă+
 În general translatoarele fac ''​cast''​ automat între pointer și array și de aceea diferența poate fi uneori greu de conştientizat. În general translatoarele fac ''​cast''​ automat între pointer și array și de aceea diferența poate fi uneori greu de conştientizat.
-Un exemplu care arata diferenta: 
-<code c> 
-// in f1.cpp: 
-extern int* v; 
-// in f2.cpp: 
-int v[10]; 
- 
-La compilare: 
-Error 1 error C2372: '​v'​ : redefinition;​ different types of indirection 
-</​code>​ 
- 
  
 Un exemplu și pentru reprezentarea și plasarea în memorie a array-urilor și pointerilor,​ inclusiv din punct de vedere al secțiunilor în care ajung aceste date este următorul: Un exemplu și pentru reprezentarea și plasarea în memorie a array-urilor și pointerilor,​ inclusiv din punct de vedere al secțiunilor în care ajung aceste date este următorul:
Line 108: Line 95:
 ==== string ==== ==== string ====
 Probabil cele mai cunoscute reprezentări:​ Probabil cele mai cunoscute reprezentări:​
-* cea din Pascal - pe primul octet se ține dimensiunea șirului, +  ​* cea din Pascal - pe primul octet se ține dimensiunea șirului, 
-* cea din C - șirul de caractere este terminat cu caracterul NULL.+  * cea din C - șirul de caractere este terminat cu caracterul NULL.
  
 ==== set ==== ==== set ====
Line 205: Line 192:
 În limbajul C, inferența de tipuri este necesară în momentul în care într-o expresie, operanzii au tipuri diferite, iar operațiile asociate expresiei sunt valide pentru mai multe din tipurile implicate în expresie. În astfel de cazuri, se aplică intern conversii între tipurile operanzilor la alte tipuri care să permită corectitudinea operațiilor,​ pe operanzi omogeni (din punct de vedere al tipurilor). Astfel de conversii se numesc conversii implicite, iar omogenitatea tipurilor operanzilor este dictată de standardele limbajului de programare (de exemplu pentru C, standardul ANSI). În limbajul C, inferența de tipuri este necesară în momentul în care într-o expresie, operanzii au tipuri diferite, iar operațiile asociate expresiei sunt valide pentru mai multe din tipurile implicate în expresie. În astfel de cazuri, se aplică intern conversii între tipurile operanzilor la alte tipuri care să permită corectitudinea operațiilor,​ pe operanzi omogeni (din punct de vedere al tipurilor). Astfel de conversii se numesc conversii implicite, iar omogenitatea tipurilor operanzilor este dictată de standardele limbajului de programare (de exemplu pentru C, standardul ANSI).
  
-Un caz foarte interesant de inferență de tip este cel din COOL, și în limbajele funcționale,​ în care o expresie returnează mereu o valoare, chiar și in cazul instrucțiunilor de bază – if, while, case. În mod evident, o astfel de expresie, cu mai multe ramuri, va returna tipul de date din care sunt derivate toate tipurile rezultatelor ramurilor. În COOL, de exemplu, a fost introdus SELF_TYPE, care este mereu evaluat la tipul clasei în care se află. Dacă o metodă virtuală a clasei de bază întoarce SELF_TYPE, ea va intoarce: 
-* când este apelată printr-o instanță a clasei de bază -> tipul clasei de bază 
-* când este apelată printr-o instanță a unei clase derivate -> tipul clasei derivate. 
  
 ===== Conversiile de tipuri ===== ===== Conversiile de tipuri =====
Line 269: Line 253:
 q = (int *) p; q = (int *) p;
 </​code>​ </​code>​
- 
- 
-====== Apelul de funcţii ====== 
-===== Transmiterea parametrilor ===== 
-În limbajele de nivel înalt, exista câteva modalități de transmitere a parametrilor și de întoarcere a rezultatelor,​ dintre care amintim: ​ 
-**NOTĂ** - Folosim termenul de argument sau argument actual pentru a ne referi la valoarea sau variabila care se pasează unei rutine și termenul de parametru sau parametru formal pentru a referi variabila căreia îi este asociată în rutina apelată 
-  * apelul prin valoare 
-  * apelul prin rezultat 
-  * apelul prin valoare-rezultat 
-  * apelul prin referinta ​ 
-  * apelul prin nume. 
-==== Apelul prin valoare ==== 
-Trimite un argument punând la dispoziția procedurii apelate valoarea lui care se asociază parametrului formal corespunzător. În timpul execuției procedurii apelate, nu există nici o interacțiune cu variabilele apelantului. Excepția apare desigur în cazul în care argumentul este un pointer și procedura apelata ar putea folosi valoarea pointerului pentru a modifica valorile către care indică. Apelul prin valoare se implementează de obicei prin copierea valorii fiecărui argument în parametrul corespunzător la intrarea în procedura apelată. Aceasta modalitate poate fi foarte eficientă în cazul argumentelor care pot fi ținute în regiștri, dar foarte ineficientă pentru vectori deoarece presupune schimb de date intens cu memoria. 
- 
-Un exemplu de limbaj în care se folosește apelul prin valoare este C, în care de fapt, acesta este singurul mecanism de pasare a parametrilor. Dar un parametru poate fi și adresa unui obiect și se obține efectul apelului prin referință. 
-În limbaje precum Java, argumentele de tipuri primare sunt trimise astfel. De asemenea, pe obiecte de tip Integer, Float, etc, se realizează unboxing înainte de trimitere, deci efectul va fi același. 
- 
-==== Apelul prin rezultat ==== 
-Este similar celui prin valoare, cu deosebirea că aici se întorc valorile de la apelat la apelant. La intrarea în procedura apelată nu se întamplă nimic, iar la întoarcere valoarea unui parametru apelat prin rezultat este pusă la dispoziția apelantului prin copierea valorii lui în argumentul asociat. Acest tip de apel apare în cazul parametrilor de tip out din Ada. 
-Exemplu in C#: 
-<code c"> 
-void a(out int result) 
-{ result = 7; /* daca lipseste atribuirea in result, da eroare de compilare */ }  
-[...] 
-// exemplu de folosire: 
-int rez; 
-a(out rez); // acum rez a primit valoarea din a. 
-</​code>​ 
- 
-==== Apelul prin valoare-rezultat ==== 
-Este caracterizat de reuniunea proprietăților celor două tipuri de apel: prin valoare și prin rezultat. La intrarea în procedura apelată valoarea argumentului se copiază în parametru, iar la ieșire valoarea parametrului se copiază înapoi în argument. Este implementat în Ada pentru parametri de tip inout. 
- 
-==== Apelul prin referinta ==== 
-Realizează o asociere între argumentul actual și parametrul corespunzator. La intrarea în procedura apelată se determină adresa argumentului și aceasta se pune la dispoziția procedurii ca mijloc de accesare a argumentului. Procedura apelată va avea acces total la argument pe toată perioada execuției sale, putând să schimbe parametrul actual și să îl transmită altor rutine pe care le apelează. Acest mecanism este foarte eficient când vectori sunt tramsmiși ca parametri, dar poate fi ineficient în cazul parametrilor de mărime mică care ar putea fi transmiși prin regiștri. Pot apărea probleme în cazul în care o constantă este transmisă prin acest mecanism. 
- 
-Dacă compilatorul implementează o constantă ca pe o locație partajată de memorie în care se stochează valoarea constantei și care se accesează la fiecare folosire a constantei într-o procedură și dacă constanta se transmite altei proceduri prin referință,​ procedura apelata ar putea modifica conținutul locației alterând astfel valoarea constantei, necesară continuării execuției procedurii apelante după revenirea din cealaltă procedură. Soluția uzuală este copierea constantelor în noi locații de memorie și transmiterea acestor adrese când se face apelul prin referință. Aceasta modalitate de apel prin referința este implementată în Fortran, dar după cum am spus, acest efect se poate realiza și în C și C++ prin transmiterea adresei ca valoare. 
- 
-<​note>​ 
-În Java, parametrii sunt transmiși prin valoare (la fel ca și în C). Acest lucru este valabil atât pentru primitive cât și pentru obiecte. 
- 
-Afirmația **obiectele se transmit prin referință** este greșită. Obiectele sunt transmise tot prin valoare, însă acestea reprezintă o referință către zona de memorie în care sunt stocate datele. Corect este **referința obiectelor este transmisă prin valoare**. 
-</​note>​ 
-==== Apelul prin nume ==== 
-Este cel mai complex mecanism de pasare a argumentelor,​ atât din punct de vedere conceptual, cât și în ceea ce privește implementarea și îl amintim doar din motive istorice deoarece ALGOL 60 este singurul limbaj care oferă acest tip de mecanism. Se poate spune că este similar cu apelul prin referință deoarece permite accesul procedurii apelate la argumentul transmis. Diferența vine din faptul că adresa argumentului este calculată la fiecare acces la acesta și nu doar o singură dată la intrarea în procedură. Astfel, dacă argumentul este a[i], și valoarea lui i se schimbă între două folosiri ale argumentului,​ la cele două folosiri se vor accesa de fapt două elemente diferite ale vectorului. 
- 
-<code pascal> 
-begin 
-  integer array a[1..2]; integer i; 
- 
-  procedure f(x, j); 
-    integer x, j; 
-    begin 
-      integer k; 
-      k := x; 
-      j := j+1; 
-      x := j; 
-      f := k; 
-    end; 
- 
-  i := 1; 
-  a[1] := 5; 
-  a[2] := 8;  
-  outinteger(a[1],​ f(a[i],i), a[2]); 
-end 
-</​code>​ 
-În exemplul de mai sus în care i și a[i] sunt transmiși de către programul principal procedurii f, prima folosire a parametrului x aduce valoarea lui a[1], pe cand cea de-a doua folosire seteaza valoarea a[2]. Apelul outinteger() va avea ca rezultat afișarea valorilor „5 5 2”. Dacă s-ar fi folosit apelul prin referința, atunci s-ar fi afișat „5 5 8”. 
- 
-===== Convenții de apel ===== 
-Apelarea unei proceduri dintr-o altă procedură presupune existența unui '​protocol'​ prin care se trece controlul de la apelant la apelat, prin care parametrii sunt pasați în aceeași direcție iar valoarea rezultatului este pasata de la apelat la apelant. În cazul unui model simplu de runtime, execuția unei proceduri constă în cinci faze, fiecare dintre acestea având mai multe subfaze: 
-  * Asamblarea argumentelor ce trebuie transferate procedurii și pasarea controlului. 
-    * Fiecare argument este evaluat și pus în registrul sau locația de pe stivă corespunzatoare;​ evaluare poate înseamna calcularea adresei lui (pentru cei pasați prin referință),​ valoarea lui (pentru cei pasați prin valoare), etc. 
-    * Se stabilește adresa codului procedurii (pentru cele mai multe limbaje însă, a fost stabilită la compilare sau linkare) 
-    * Regiștrii care au fost folosiți și vor mai fi și după întoarcerea din procedura apelată, se stochează în memorie dacă protocolul specifică că este datoria apelantului sa facă acest lucru 
-    * Se salvează adresa de întoarcere și se execută un salt la adresa codului procedurii (de obicei o instrucțiune ''​call''​ face aceste lucruri) 
-  * Prologul procedurii, executat la întrarea în procedură, stabilește mediul necesar adresării și poate salva regiștrii folosiți de procedură în scopuri proprii 
-  * Se executa procedura, care la rândul ei poate apela alte proceduri 
-  * Epilogul procedurii restaurează valorile regiștrilor și mediul de adresare al apelantului,​ asamblează valoarea pe care trebuie să o întoarcă și îi redă controlul apelantului 
-    * Regiștrii salvați de procedura apelată sunt restaurați din memorie 
-    * Valoarea care trebuie întoarsă se pune în locul corespunzator (dacă procedura întoarce o valoare) 
-    * Se incarcă adresa de revenire și se executa un salt la această adresă (de obicei, o instrucțiune ''​ret''​ face acest lucru) 
-  * Codul din procedura apelantă care se afla după apel își termină restaurarea mediului sau de execuție și primește valoarea intoarsă 
-    * Regiștrii salvați de către procedura apelantă sunt restaurați din memorie 
-    * Se folosește valoarea întoarsă de procedura apelată. 
- 
-===== Prototipuri de funcții ===== 
-Un prototip de funcție reprezintă o declarare a funcției care omite corpul funcției, dar specifică numele, tipul parametrilor și tipul întors. În timp ce definiția unei funcții specifică ce face o funcție, prototipul funcției poate fi descris ca fiind interfața funcției. Acest lucru este pus în evidență în IDE-urile care au opțiuni diferite “Go to declaration” și “Go to definition”. 
- 
-Prototipurile sunt foarte importante, pentru că ele sunt folosite de compilator pentru a determina caracteristicile unei funcții ce urmează a fi apelata pentru a i se putea transmite parametrii sau a se putea primi valoarea întoarsă de funcție. De asemenea, prototipul funcției este folosit și în funcția apelată pentru primirea parametrilor de la funcția apelantă, precum și pentru transmiterea valorii întoarse de funcție. 
- 
-Dacă prototipul funcției vizibil în locul sau locurile în care funcția este apelata diferă de prototipul functiei din locul în care funcția este definita, atunci se pot ajunge la apeluri de funcție eronate, datorate incompatibilității între numărul și tipul parametrilor din locul apelării funcției și din funcția propriu-zisă. 
- 
-Din punct de vedere al compilatorului,​ dacă o funcție nu este declarată, ea va avea automat atribuit prototipul implicit al funcțiilor:​ 
- 
-<code c> 
-// pentru apeluri de tipul 
-func(); x = func(); 
-// semnătura implicită este 
-int func(void); 
-</​code> ​ 
- 
-Pentru funcții care sunt apelate cu argumente semnătura implicită este  
-<code c> 
-func(x, y, z); 
-// 
-int func(typeof_x,​ typeof_y, typeof_z); 
-</​code>​ 
-**ATENȚIE**:​ dacă tipurile argumentelor sunt mai mici decât un ''​int'',​ compilatorul de C va face upcast parametrului la int și apoi va chema funcția. Dacă implementarea funcției se aștepta să îi fi fost trimiși mai puțini bytes se poate corupe memoria. Exemplu: 
-<code c> 
-char c = '​a';​ 
-short s = 1; 
-f(c, s); 
- 
-// semnătură implicită generată: ​ 
-int f(int, int); 
-</​code>​ 
-Presupunând că parametrii sunt trimiși pe o stivă adresabilă la nivel de octet și că ''​sizeof(char) = 1'',​ ''​sizeof(short) = 2''​ și ''​sizeof(int) = 4''​ avem următoarea diferență între ce așteptă funcția ''​f''​ să primească ca parametri și ce argumente au fost pasate pe stivă. 
-| parametri trimiși la apel | c0 | c1 | c2 | c3 | s0 | s1 | s2 | s3 | 
-| parametri așteptați de implementare | c1 | s0 | s1 | 
- 
- 
- 
-în general compilatoarele fac verificări de compatibilitate între prototipurile unei aceleiași funcții, dar există cazuri cand aceste verificări nu sunt posibile (de exemplu când se folosesc funcții din biblioteci). 
- 
-Un exemplu de functionare eronată a unui apel de funcție din motive de incompatibilitate între prototipurile unei funcții: 
-<code c> 
-//        fisier1.c 
-/* func() nedeclarata ​                  ​prototip implicit 
-                                        int func(void) */ 
-int main() 
-{ 
-    /* ... */ 
-    value += func(); 
-    /* ... */ 
-} 
-</​code>​ 
-<code c> 
-// fisier2.c 
-/* prototip int func(int, int) */ 
-long long func(int param1, int param2) 
-{ 
-    return (long long) param1 + param2; 
-} 
-</​code>​ 
-În acest caz, în locul de unde se face apelul (din funcția main) se foloseşte prototipul implicit, care împreună cu un apel greşit de funcție, duce la cod eronat: 
-  * în locul de unde se face apelul, nu se transmite niciun parametru, și se aşteaptă să se întoarcă o valoare de tip int, 
-  * în funcția apelată, se așteapta 2 parametri care vor fi citiți eronat din locațiile corespunzătoare,​ iar valoarea întoarsă va fi un long long.  
- 
-Cea mai frecventă eroare din punct de vedere al incompatibilității între prototipuri este aceea a nedeclarării prototipurilor în cazul unor funcții de bibliotecă,​ fapt ce duce la folosirea prototipului implicit și la imposibilitatea verificării compatibilității între prototipuri. 
- 
-Este important de observat, totuși, ca majoritatea limbajelor moderne (inclusiv C++, versus C) nu acceptă tipuri si prototipuri implicite. 
- 
-===== Cadre de stiva (stack frames) ===== 
- 
-Deși ar fi ideal ca toți operanzii să fie ținuți în regiștri, majoritatea procedurilor necesită spatiu de memorie pentru: 
-  * variabilele care fie nu au primit regiștri, fie nu pot fi ținute în regiștri pentru că le sunt încărcate adresele (explicit sau implicit, ca in cazul parametrilor transferati prin referință) sau pentru că trebuie să fie indexabile 
-  * a oferi un loc "​standard"​ pentru a salva valorile din regiștri atunci când se execută un apel de procedură 
-  * a oferi unui debugger o modalitate de a cunoaște lanțul de proceduri active la un moment dat (call stack) 
- 
-Deoarece aceste spații de memorie devin necesare la intrarea într-o procedură și devin inutile la ieșirea din aceasta, ele sunt grupate în cadre de stivă(activation records sau stack frames), care la randul lor sunt organizate sub formă de stiva. Un cadru de stivă poate conține: 
-  * valorile parametrilor trimiși rutinei curente care nu încap în regiștrii destinați acestui scop,  
-  * toate sau o parte din variabilele locale, ​ 
-  * o zona de salvare a regiștrilor temporari alocați de compilator, 
-  * adresa de întoarcere din procedura etc. 
- 
-Pentru a putea accesa locatiile din cadrul de stivă curent în timpul execuției, acestora li se asociază distante în unitati de memorie relative la un pointer stocat într-un registru. Pointerul poate fi frame pointer-ul ''​fp''​ care indică prima locație a cadrului curent, sau stack pointer-ul ''​sp''​ care indică vârful stivei, adica imediat dupa ultima locație a cadrului curent. Numeroase compilatoare aranjează în memorie cadrele de stivă astfel încat începutul acestora se află la adrese mai mari de memorie decât sfârșitul acestora. Această alegere face ca deplasamentul față de sp-ul curent în cadrul curent să fie pozitiv, așa cum se vede în figura de mai jos. 
- 
-{{ :​cpl:​labs:​laborator-05-stack-sp.png?​480 |}} 
- 
-Unele compilatoare folosesc amândoi pointerii, ''​sp''​ și fp, având variabile relative la amândoi regiștrii. 
- 
-{{ :​cpl:​labs:​laborator-05-stack-fp.png?​480 |}} 
- 
-Caracteristicile limbajului și cele ale hardware-lui determină alegerea unuia dintre acești registri sau chiar folosirea ambilor pentru accesarea locațiilor de pe stivă. Problemele sunt urmatoarele:​ 
-  * folosirea a doi regiștri pentru accesul la stivă înseamnă a consuma un registru care ar putea folosi altor scopuri; 
-  * dacă deplasamentul scurt față de un singur registru oferit de instrucțiunile load (încărcare din memorie) și store (salvare în memorie) este suficient pentru a acoperi spatiul de memorie al majorității înregistrărilor de activare (ceea ce se întâmplă pentru cele mai multe  arhitecturi). 
-  * dacă dimensiunea cadrului de stivă este sau nu cunoscută la momentul compilării,​ și dacă se modifică pe parcursul execuției procedurii. 
- 
-Dimensiunea unui cadru de stivă nu este cunoscută, de exemplu, dacă trebuie să fie suportate funcții de alocare a memoriei de tipul ''​alloca()''​ din biblioteca C prin care se alocă spațiu în mod dinamic în cadrul de stivă curent, și se întoarce un pointer la acel spațiu. 
- 
-Prin urmare, este suficient și de preferat să se folosească doar sp-ul, în cazul în care nu trebuie să se ofere suport pentru funcții de tipul ''​alloca()''​. 
- 
-Efectul unui apel ''​alloca()''​ este să extindă cadrul de stivă făcând sp-ul să indice la o locație nouă. Prin urmare, deplasamentele relative la sp vor pointa către alte locații. Deoarece în C se poate calcula adresa unei variabile locale și apoi folosi, se impune ca acele cantități accesate relativ la sp să nu poată fi direct adresabile de catre utilizator și se preferă ca acestea să fie valori necesare la apelul unei alte proceduri. ​ 
- 
-Astfel, în momentul când există fp, adresarea relativa la sp se foloseste pentru: 
-  * transmiterea argumentelor unei alte proceduri, ​ 
-  * salvarea regiștrilor peste un apel de procedură 
-  * întoarcerea rezultatelor dintr-o altă procedură. ​ 
- 
-Pentru a oferi suport pentru ''​alloca()''​ avem nevoie atât de un registru sp cât și de unul fp. Chiar dacă aceasta necesita înca un registru în plus, exista avantajul execuției rapide în cazul apelurilor de proceduri. Astfel, la intrarea în procedură: 
-  * se salvează vechiul fp pe stivă, în cadrul noului cadru, 
-  * se setează valoarea noului fp la valoarea vechiului sp și 
-  * adaugă lungimea cadrului curent la vechiul sp pentru a obține noua valoare a lui sp. 
- 
-La întoarcerea din procedură, procesul invers este: 
-  * se setează valoarea noului sp la valoarea vechiului fp 
-  * se încarcă noul fp de pe stivă 
- 
-{{ :​cpl:​labs:​laborator-05-stack-full.png?​480 |}} 
- 
- 
  
 ====== Exerciții ====== ====== Exerciții ======
-{{:​cpl:​labs:​laborator-05-arhiva.zip|Arhiva}} laboratorului+{{:​cpl:​labs:​lab06_skel.zip|Arhiva}} laboratorului. 
-  - Scrieți un program ''​C''​ care să determine sensul stivei (este mărită sau micșorată valoarea adresei vârfului stivei la operații de ''​push''?​)+  - De ce nu există diferențe pentru definițiile lui v între următoarele semnături de funcții? <code c> void f(int * v); </​code> ​ și <code c> void g(int v[]); </​code>​ 
-  - De ce nu există diferențe pentru definițiile lui v între următoarele semnături de funcții? +  - Intrati in directorul ''​diff_vec_ptr1''​. ​Rulati ​''​make'' ​apoi rulati programulCare este problema? Incercati sa explicati apoi rezolvati bug-ulPuteti vedea acelasi comportament si ruland codul din directorul ​''​diff_vec_ptr2''​.
-<code c> void f(int * v); </​code> ​ și <code c> void g(int v[]); </​code>​ +
-  - În directorul ''​return_struct'' ​din arhiva laboratorului aveți implementate două funcții: una care întoarce o structură și alta care primește o structură ca parametruRulați ​''​make ​asm'' ​pentru a genera un fișier ".s" și explicați:​ +
-    * modul în care se trimite un argument de tip structură,​ +
-    * modul în care se întoarce o structură. +
-  - Modificați ​''​doar'' ​funcția **ask** din programul **ovr** pentru a afișa nota corectă.+
   - Determinați (inspectând codul asm) cum sunt implementați [[http://​gcc.gnu.org/​onlinedocs/​gcc/​Variable-Length.html | vectorii automatici de lungime variabilă]]. Pentru vectori automatici de lungime constantă ''​sizeof(v)''​ reprezintă toată memoria alocată (în bytes) pentru acel vector - o constantă cunoscută la momentul compilării. Cum este implementat operatorul ''​sizeof''​ pentru vectori de lungime variabilă? Folosiți codul din directorul ''​alloca''​ din arhiva laboratorului.   - Determinați (inspectând codul asm) cum sunt implementați [[http://​gcc.gnu.org/​onlinedocs/​gcc/​Variable-Length.html | vectorii automatici de lungime variabilă]]. Pentru vectori automatici de lungime constantă ''​sizeof(v)''​ reprezintă toată memoria alocată (în bytes) pentru acel vector - o constantă cunoscută la momentul compilării. Cum este implementat operatorul ''​sizeof''​ pentru vectori de lungime variabilă? Folosiți codul din directorul ''​alloca''​ din arhiva laboratorului.
-  - Scrieți o funcție ''​fast_multiply_3''​ în asm care să înmulțească trei numere întregi primite ca argumente. Declarați funcția ca fiind de tip ''​fastcall''​ (Pe arhitecturi Intel x86, folosirea conveției de apel fastcall, presupune că: 
-    * primul argument se află în ''​%ecx''​ 
-    * al doilea argument se află în ''​%edx''​ 
-    * toți ceilalți parametri se află pe stivă ca la convenția de apel 
-    * Următoarea linie declară o funcție specificându-i convenție de apel de tip ''​fastcall''​ 
-    * <code c> __attribute__((fastcall)) int fast_multiply_3(int a, int b, int c); </​code>​ 
-    * Apelați funcția definită la pasul anterior dintr-un fișier ''​C''​. Observați și explicați ce se întâmplă dacă în cadrul fișierului ''​C''​ nu apare declarația funcției ''​fast_multiply_3''​ sau dacă acestei declarații îi lipsește decoratorul ''​fastcall''​. 
-    * Pentru operația de înmulțire folosiți instrucțiunea 
-    * <code asm> imull %eax, %ecx # echivalent cu %ecx := %ecx * %eax </​code>​ 
   - Scrieți un program care să determine ordinea secțiunilor în memorie.   - Scrieți un program care să determine ordinea secțiunilor în memorie.
     * ''​Hint'':​ Declarați obiecte în fiecare secțiune și verificați adresa.     * ''​Hint'':​ Declarați obiecte în fiecare secțiune și verificați adresa.
 +  - Intrati in directorul ''​classes''​. Fara a modifica functia main, faceti schimbari in fisierul ''​classes.cpp''​ astfel incat sa apara "​different"​. Scoateti cast-urile catre void *. De ce afiseaza din nou "​same"?​
   - Modificați programul următor pentru a afișa **structurile sunt identice**:   - Modificați programul următor pentru a afișa **structurile sunt identice**:
 <file c ex8.c> <file c ex8.c>
Line 520: Line 291:
 </​file>​ </​file>​
  
 +===== BONUS =====
 +Intrati in directorul ''​bonus'',​ compilati si rulati programul. De ce primim ''​Segmentation fault''?​ In ce sectiuni se gasesc "Hello 1", "Hello 2", pc1, respectiv pc2?
  
cpl/labs/06.1447105029.txt.gz · Last modified: 2015/11/09 23:37 by laura.vasilescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0