This shows you the differences between two versions of the page.
|
programare:laboratoare:lab06 [2021/10/24 14:14] andrei.traistaru99 [Exerciţii de Laborator] |
programare:laboratoare:lab06 [2025/11/10 07:50] (current) teodor.birleanu [Vectori alocaţi dinamic] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Matrice. Operaţii cu matrice: adunare, înmulţire. Reprezentarea în memorie. ===== | + | ===== PCLP Laborator06: Alocarea dinamică a memoriei. Aplicații folosind tablouri ===== |
| **Responsabili:** | **Responsabili:** | ||
| - | * [[neatudarius@gmail.com|Darius Neațu (CA 2019-2020)]] | + | * [[neatudarius@gmail.com|Darius Neațu (2019-2020)]] |
| - | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (CA 2019-2020)]] | + | * [[ion_dorinel.filip@cti.pub.ro|Dorinel Filip (2019-2020)]] |
| - | * [[andrei.parvu@cti.pub.ro|Andrei Pârvu]] | + | * [[teodor.matei.birleanu@gmail.com|Bîrleanu Teodor Matei (2025 - prezent)]] |
| + | * [[laura.vasilescu@cti.pub.ro|Laura Vasilescu]] | ||
| + | * [[murarugeorgec@gmail.com|George Muraru]] | ||
| - | [[https://docs.google.com/document/d/16GNacxqmnRjaBx7w8tt9rRprbpiOjx8njzIrv4Q0mI0/edit?usp=sharing|Probleme]] | ||
| - | [[https://we.tl/t-HTa9YV1Wvg|Teste]] | + | ===== Obiective ===== |
| - | ==== Obiective ===== | + | |
| În urma parcurgerii acestui laborator studentul va fi capabil: | În urma parcurgerii acestui laborator studentul va fi capabil: | ||
| - | * să declare matrice si orice fel de tablou multidimensional; | + | * să aloce dinamic o zona de memorie; |
| - | * sa initializeze aceste structuri, atat din declaratie, cat si prin instructiuni iterative; | + | * să elibereze o zona de memorie; |
| - | * sa cunoasca regulile de reprezentare ale tablourilor in memorie si sa inteleaga modul in care compilatorul interpreteaza operatorii de indexare; | + | * să lucreze cu vectori şi matrice alocate dinamic. |
| - | * sa cunoasca scheme comune de utilizare a acestor structuri; | + | |
| - | * sa foloseasca practici recunoscute si recomandate pentru scrierea de cod sursa care implica lucrul cu matrice; | + | |
| - | * sa recunoasca si sa evite erorile comune de programare legate de aceste structuri | + | |
| + | ===== Funcţii de alocare şi eliberare a memoriei ===== | ||
| + | Funcțiile standard de alocare și de eliberare a memoriei sunt declarate în fişierul antet ''stdlib.h''. | ||
| + | * ''void *malloc(size_t size);'' | ||
| + | * ''void *calloc(size_t nmemb, size_t size);'' | ||
| + | * ''void *realloc(void *ptr, size_t size);'' | ||
| + | * ''void free(void *ptr);'' | ||
| - | ==== Noţiuni teoretice ==== | + | ==== Alocarea memoriei ===== |
| - | === Matrice === | + | Cele trei funcţii de alocare (''malloc'', ''calloc'' și ''realloc'') au ca rezultat adresa zonei de memorie alocate (de tip ''void*'') şi ca argument comun dimensiunea, în octeţi, a zonei de memorie alocate (de tip ''size_t'' ). Dacă cererea de alocare nu poate fi satisfăcută pentru că nu mai există un bloc continuu de dimensiunea solicitată, atunci funcţiile de alocare au rezultat ''NULL'' (ce reprezintă un pointer de tip ''void *'' la adresa de memorie 0, care prin convenţie este o adresă nevalidă - nu există date stocate în acea zonă). |
| - | Matricea este o colecţie omogenă şi bidimensională de elemente. Acestea pot fi accesate prin intermediul a doi indici, numerotaţi, ca şi în cazul vectorilor, începand de la 0. Declaraţia unei matrice este de forma: | + | == Exemplu == |
| - | + | <code c> | |
| - | <code bash> | + | char *str = malloc(30); // Aloca memorie pentru 30 de caractere |
| - | <tip_elemente> <nume_matrice>[<dim_1>][<dim_2>]; | + | int *a = malloc(n * sizeof(int)); // Aloca memorie pt. n numere intregi |
| </code> | </code> | ||
| - | De exemplu, avem: | + | <note>Dimensiunea memoriei luată ca parametru de ''malloc()'' este specificată în ''octeţi'', indiferent de tipul de date care va fi stocat în acea regiune de memorie! Din acest motiv, pentru a aloca suficientă memorie, numărul dorit de elemente trebuie înmulţit cu dimensiunea unui element, atunci când are loc un apel ''malloc()''.</note> |
| + | |||
| + | |||
| + | Alocarea de memorie pentru un vector şi iniţializarea zonei alocate cu zerouri se poate face cu funcţia ''calloc''. | ||
| + | == Exemplu == | ||
| <code c> | <code c> | ||
| - | int mat[5][10]; | + | int *a = calloc(n, sizeof(int)); // Aloca memorie pentru n numere intregi și inițializează zona cu zero |
| </code> | </code> | ||
| + | |||
| + | Codul de mai sus este perfect echivalent (dar mai rapid) cu următoarea secvenţă de instrucţiuni: | ||
| <code c> | <code c> | ||
| - | #define MAX_ELEM 100 | + | int i; |
| - | float a[MAX_ELEM][MAX_ELEM]; | + | int *a = malloc(n * sizeof(int)); |
| + | for (i = 0; i < n; i++) { | ||
| + | a[i] = 0; | ||
| + | } | ||
| </code> | </code> | ||
| - | Numărul de elemente ale unei matrice va fi ''dim_1*dim_2'', şi semnificaţia fiecărei dimensiuni este o chestiune ce ţine de logica programului. În matematică, prima dimensiune poate să însemne linia şi a doua coloana pentru fiecare element, însa acest lucru nu este obligatoriu. Este necesar totuşi, pentru funcţionarea corectă a programului, să se respecte semnificaţiile alese pe întreg parcursul codului sursă. | + | <note>În timp ce funcţia ''malloc()'' ia un singur parametru (o dimensiune în octeţi), funcţia ''calloc()'' primeşte două argumente, o lungime de vector şi o dimensiune a fiecărui element. Astfel, această funcţie este specializată pentru memorie organizată ca un vector, în timp ce ''malloc()'' nu ţine cont de structura memoriei.</note> |
| - | + | <note warning>Acest lucru aduce și o măsură de siguranță în plus deoarece înmulțirea dintre numărul de elemente și dimensiunea tipului de date ar putea face overflow, iar dimensiunea memoriei alocate să nu fie în realitatea cea așteptată.</note> | |
| - | === Tablouri multidimensionale === | + | |
| - | + | ||
| - | Vectorii şi matricele se pot extrapola la noţiunea generală de tablou cu mai multe dimensiuni, care se declară în modul următor: | + | |
| - | + | ||
| - | <code bash> | + | |
| - | <tip_elemente> <nume_tablou>[<dim_1>][<dim_2>]...[<dim_n>]; | + | |
| - | </code> | + | |
| - | De exemplu: | + | Realocarea unui vector care creşte (sau scade) faţă de dimensiunea estimată anterior se poate face cu funcţia ''realloc'', care primeşte adresa veche şi noua dimensiune şi întoarce **noua adresă**: |
| <code c> | <code c> | ||
| - | int cube[3][3][3]; | + | int *aux; |
| + | aux = realloc(a, 2 * n * sizeof(int)); // Dublare dimensiune anterioara (n) | ||
| + | if (aux) //daca aux este diferit de NULL | ||
| + | a = aux; | ||
| + | else | ||
| + | //prelucrare in caz de eroare | ||
| </code> | </code> | ||
| - | Deşi, în cazul a mai mult de 3 dimensiuni, tablourile pot să nu mai aibă sens concret sau fizic, acestea pot fi deosebit de utile în multe situaţii. În acest laborator ne vom rezuma totuşi la tablouri bidimensionale. | + | În exemplul anterior, noua adresă este memorată tot în variabila pointer ''a'', înlocuind vechea adresă (care nu mai este necesară şi nici nu mai trebuie folosită). Funcţia ''realloc()'' realizează următoarele operaţii: |
| + | * alocă o zonă de dimensiunea specificată ca al doilea argument | ||
| + | * copiază la noua adresă datele de la adresa veche (primul argument al funcţiei) | ||
| + | * eliberează memoria de la adresa veche. | ||
| + | In cazul in care nu reuseste sa aloce memorie functia ''realloc()'' intoarce NULL lasand pointerul initial nemodificat. Din acest motiv verificam daca realocarea a reusit si apoi asignam rezultatul pointerului ''a''. | ||
| - | === Adunarea si înmulţirea matricelor === | + | ==== Eliberarea memoriei ===== |
| - | == Suma matricelor == | + | Funcţia ''free()'' are ca argument o adresă (un pointer) şi eliberează zona de la adresa respectivă (alocată prin apelul unei funcţii de tipul ''[m|c|re]alloc''). Dimensiunea zonei nu mai trebuie specificată deoarece este ţinută minte de sistemul de alocare de memorie în nişte structuri interne. |
| - | Fie $A \in {\mathbb R}^{m \times n}$, $B \in {\mathbb R}^{m \times n}$, atunci $(A+B) \in {\mathbb R}^{m \times n}$, unde: $(A+B)_{i,j} = A_{i,j} + B_{i,j}$ | + | ===== Vectori alocaţi dinamic ===== |
| - | {{:programare:laboratoare:sum.png|}} | + | Structura de vector are avantajul simplităţii şi economiei de memorie faţă de alte structuri de date folosite pentru memorarea unei colectii de informaţii între care există anumite relaţii. Între cerinţa de dimensionare constantă a unui vector şi generalitatea programelor care folosesc astfel de vectori există o contradicţie. De cele mai multe ori programele pot afla (din datele citite) dimensiunile vectorilor cu care lucrează şi deci pot face o alocare dinamică a memoriei pentru aceşti vectori. Aceasta este o solutie mai flexibilă, care foloseşte mai bine memoria disponibilă şi nu impune limitări arbitrare asupra utilizării unor programe. În limbajul C nu există practic nici o diferenţă între utilizarea unui vector cu dimensiune fixă şi utilizarea unui vector alocat dinamic, ceea ce încurajează şi mai mult utilizarea unor vectori cu dimensiune variabilă. |
| - | Exemplu: | + | == Exemplu == |
| + | <code c> | ||
| + | int main(void) | ||
| + | { | ||
| + | int n,i; | ||
| + | int *a; // Adresa vector alocat dinamic | ||
| - | $$ | + | printf("n = "); |
| - | \Large | + | scanf("%d", &n); // Dimensiune vector |
| - | \begin{bmatrix} | + | |
| - | 1 & 3 \\ | + | |
| - | 0 & 4 \\ | + | |
| - | 5 & 8 | + | |
| - | \end{bmatrix} | + | |
| - | + | + | |
| - | \begin{bmatrix} | + | |
| - | 2 & 5 \\ | + | |
| - | 1 & 2 \\ | + | |
| - | 6 & 1 | + | |
| - | \end{bmatrix} | + | |
| - | = | + | |
| - | \begin{bmatrix} | + | |
| - | 3 & 8 \\ | + | |
| - | 1 & 6 \\ | + | |
| - | 11 & 9 | + | |
| - | \end{bmatrix} | + | |
| - | $$ | + | |
| - | == Înmulţirea matricelor == | + | a = calloc(n, sizeof(int)); // Alternativ: a = malloc(n * sizeof(int)); |
| + | printf("Componente vector: \n"); | ||
| + | |||
| + | for (i = 0; i < n; i++) { | ||
| + | scanf("%d", &a[i]); // Sau scanf (“%d”, a+i); | ||
| + | } | ||
| + | for (i = 0; i < n; i++) { // Afisare vector | ||
| + | printf("%d ",a[i]); | ||
| + | } | ||
| - | Fie $A \in {\mathbb R}^{m \times n}$, $B \in {\mathbb R}^{n \times p}$, atunci $(AB) \in {\mathbb R}^{m \times p}$, unde elementele $A.B$ sunt date de formula: | + | free(a); // Nu uitam sa eliberam memoria |
| - | $(AB)_{i,j} = \sum_{r=1}^n A_{i,r}B_{r,j}$ | + | |
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| - | {{:programare:laboratoare:mul.png|}} Exemplul din stânga prezintă cum se calculează valorile (1,2) si (3,3) ale '''AB''' daca '''A''' este o matrice 3×2, si '''B''' o matrice 2×3. Pentru calculul unui element din matrice se consideră o linie respectiv o coloană din fiecare matrice conform săgeţilor. Elementele din acestea sunt înmulţite câte 2 conform înmulţirii pe vectori, apoi suma produselor constituie elementul din matricea finală | + | Există şi cazuri în care datele memorate într-un vector rezultă din anumite prelucrări, iar numărul lor nu poate fi cunoscut de la începutul execuţiei. Un exemplu poate fi un vector cu toate numerele prime mai mici ca o valoare dată. În acest caz se poate recurge la o realocare dinamică a memoriei. În exemplul următor se citeşte un număr necunoscut de valori întregi într-un vector extensibil: |
| + | <code c> | ||
| + | #define INCR 100 // cu cat creste vectorul la fiecare realocare | ||
| - | $$ | + | int main(void) |
| - | (\mathbf{AB})_{1,2} = \sum_{r=1}^2 a_{1,r}b_{r,2} = a_{1,1}b_{1,2}+a_{1,2}b_{2,2} \\ | + | { |
| - | (\mathbf{AB})_{3,3} = \sum_{r=1}^2 a_{3,r}b_{r,3} = a_{3,1}b_{1,3}+a_{3,2}b_{2,3} | + | int n, i, m; |
| - | $$ | + | float x, *v, *tmp; // v = adresa vector |
| + | | ||
| + | n = INCR; | ||
| + | i = 0; | ||
| - | Exemplu: | + | v = malloc(n * sizeof(float)); // Dimensiune initiala vector |
| - | + | if (v == NULL) { | |
| - | $$ | + | /* Nu s-a reusit alocarea */ |
| - | \Large | + | printf("Could not allocate v\n"); |
| + | return 1; | ||
| + | } | ||
| | | ||
| - | \begin{bmatrix} | + | while (scanf("%f", &x) != EOF) { |
| - | 1 & 0 & 2 \\ | + | if (i == n) { // Daca este necesar... |
| - | -1 & 3 & 1 | + | n = n + INCR; // ... creste dimensiune vector |
| - | \end{bmatrix} | + | tmp = realloc(v, n * sizeof(float)); |
| - | \cdot | + | if (tmp != NULL) { |
| - | \begin{bmatrix} | + | /* Daca s-a reusit alocarea pentru noua zona de memorie */ |
| - | 3 & 1 \\ | + | v = tmp; |
| - | 2 & 1 \\ | + | } else { |
| - | 1 & 0 | + | /* Daca nu s-a reusit alocarea */ |
| - | \end{bmatrix} | + | break; |
| - | = | + | } |
| - | \begin{bmatrix} | + | } |
| - | 1 \times 3 + 0 \times 2 + 2 \times 1 & 1 \times 1 + 0 \times 1 + 2 \times 0 \\ | + | |
| - | -1 \times 3 + 3 \times 2 + 1 \times 1 & -1 \times 1 + 3 \times 1 + 1 \times 0 | + | |
| - | \end{bmatrix} | + | |
| - | = | + | |
| - | \begin{bmatrix} | + | |
| - | 5 & 1 \\ | + | |
| - | 4 & 2 | + | |
| - | \end{bmatrix} | + | |
| - | $$ | + | |
| - | === Reprezentarea în memorie === | + | v[i++] = x; // Memorare in vector numar citit |
| + | } | ||
| - | Cunoaşterea reprezentării în memorie a tablourilor vă ajută să înţelegeţi mai bine cum se lucrează cu aceste tipuri de date şi să evitaţi atât erorile comune, cât şi pe cele mai subtile. Aşa cum se ştie, fiecare variabilă are asociata o anumită adresă în memorie şi ocupă o anumită lungime, măsurată în octeţi. Standardul C impune ca un tablou să fie memorat într-o zonă continuă de memorie, astfel ca pentru un tabloul de forma: ''T tab[dim1][dim2]...[dimn];'' | + | m = i; |
| - | dimensiunea ocupată în memorie va fi ''sizeof(T)*dim1*dim2*...*dimn''. Vom considera în continuare cazul particular al unui vector vect de lungime n, şi al unui element oarecare al acestuia, de pe pozitia i. | + | |
| - | Atunci când întalneşte numele vect, compilatorul va intelege ''"adresa în memorie de la care începe vectorul vect"''. Operatorul de indexare [] aplicat numelui vect instruieşte compilatorul să ''"evalueze acel element de tipul T, care se află pe pozitia i în vectorul care începe de la adresa vect"''. Acest lucru se poate exprima direct: ''"evaluarea variabilei de tip T de la adresa vect + i * sizeof(T)"''. | + | |
| - | În ultima formulare observaţi ca nu mai intervine sub nici o formă dimensiunea vectorului dată la declarare. Aceea a fost necesară doar compilatorului, ca sa ştie câtă memorie să aloce pentru reprezentarea acestuia. De asemenea, observaţi că sunt permise indexari în afara spaţiului de memorie alocat, şi astfel programul va putea, din greşeala, accesa alte zone de memorie, lucru care poate avea repercursiuni grave. În cel mai bun caz programul nostru se va comporta foarte ciudat (erori în locuri total imprevizibile), şi în cel mai rău caz întreg sistemul va fi blocat (în cazul sistemelor care nu au implementate spaţii virtuale de memorie proprii fiecărei aplicaţii - platformele Windows NT si Linux). | + | for (i = 0; i < m; i++) { // Afisare vector |
| + | printf("%f ", v[i]); | ||
| + | } | ||
| + | printf("\n"); | ||
| - | Faptul că graniţa dintre vectori şi adrese de memorie este atât de fină în limbajul C, sintaxa acestuia permite expresii ciudate, de forma: | + | free(v); |
| - | <code c> | + | return 0; |
| - | char a[100]; | + | } |
| - | a[0] = 1; | + | |
| - | 3[a] = 5; | + | |
| </code> | </code> | ||
| - | Instrucţiunea din urmă înseamna pur şi simplu "asignează 5 variabilei de tip char de la adresa 3 + a * sizeof(char) = 3 + a". Observaţi că aceasta este echivalentă cu a[3] = 5; | ||
| - | |||
| - | De asemenea, un alt avantaj apare la definirea unui parametru al unei funcţii, de tip vector, caz în care nu este necesară precizarea dimensiunii acestuia: | ||
| - | void sort(int[] vect, n); | ||
| - | <note> | + | ===== Matrice alocate dinamic ===== |
| - | Este de remarcat faptul că pentru **tablouri de dimensiuni m > 1**, este necesară precizarea **lungimilor ultimelor m - 1 dimensiuni**, pentru ca compilatorul să poată calcula adresa fiecărui element atunci când acesta este referit în program. | + | |
| - | Mai multe detalii legate de reprezentarea tablourilor şi, în general, a datelor în memorie, vor fi date în laboratorul 8. | + | |
| - | </note> | + | |
| - | === Exemple de programe === | + | Alocarea dinamică pentru o matrice este importantă deoarece: |
| + | * foloseşte economic memoria şi evită alocări acoperitoare, estimative. | ||
| + | * permite matrice cu linii de lungimi diferite (denumite uneori ''ragged arrays'', datorită formelor "zimţate" din reprezentările grafice) | ||
| + | * reprezintă o soluţie bună la problema argumentelor de funcţii de tip matrice. | ||
| + | |||
| + | Daca programul poate afla numărul efectiv de linii şi de coloane al unei matrice (cu dimensiuni diferite de la o execuţie la alta), atunci se va aloca memorie pentru un vector de pointeri (funcţie de numărul liniilor) şi apoi se va aloca memorie pentru fiecare linie (funcţie de numărul coloanelor) cu memorarea adreselor liniilor în vectorul de pointeri. O astfel de matrice se poate folosi la fel ca o matrice declarată cu dimensiuni constante. | ||
| - | * Declararea unei matrici unitate: | + | == Exemplu == |
| <code c> | <code c> | ||
| - | #define M 20 /* nr maxim de linii si de coloane */ | + | int main (void) |
| + | { | ||
| + | int **a; | ||
| + | int i, j, nl, nc; | ||
| + | |||
| + | printf("nr. linii = "); | ||
| + | scanf(“%d”, &nl); | ||
| - | int main() { | + | printf("nr. coloane = "); |
| - | float unit[M][M]; | + | scanf(“%d”, &nc); |
| - | int i,j,n; | + | |
| - | + | /* | |
| - | printf("nr.linii/coloane: "); | + | * In cele ce urmeaza presupunem ca toate apelurile de alocare de memorie |
| - | scanf("%d", &n); | + | * nu vor esua. |
| - | if (n > M) { | + | */ |
| - | return; | + | a = malloc(nl * sizeof(int *)); // Alocare pentru vector de pointeri |
| + | |||
| + | for (i = 0; i < nl; i++) { | ||
| + | a[i] = calloc(nc, sizeof(int)); // Alocare pentru o linie si initializare la zero | ||
| } | } | ||
| - | | + | |
| - | for (i = 0; i < n; i++) { | + | // Completare diagonala matrice unitate |
| - | for (j = 0; j < n; j++) { | + | for (i = 0; i < nl; i++) { |
| - | if (i != j) { | + | a[i][i] = 1; // a[i][j]=0 pentru i != j |
| - | unit[i][j]=0; | + | |
| - | } else { | + | |
| - | unit[i][j]=1; | + | // Afisare matrice |
| - | } | + | printmat(a, nl, nc); |
| - | } | + | |
| - | } | + | for (i = 0; i < nl; i++) |
| - | | + | free(a[i]); |
| + | free(a); // Nu uitam sa eliberam! | ||
| return 0; | return 0; | ||
| } | } | ||
| </code> | </code> | ||
| - | * Citire/scriere de matrice de reali: | + | Funcţia de afişare a matricei se poate defini astfel: |
| <code c> | <code c> | ||
| - | int main() { | + | void printmat(int **a, int nl, int nc) { |
| - | int nl, nc, i, j; | + | |
| - | float a[20][20]; | + | |
| - | + | ||
| - | /* Citire de matrice */ | + | |
| - | printf("nr.linii: "); | + | |
| - | scanf("%d", &nl); | + | |
| - | printf("nr.coloane: "); | + | |
| - | scanf("%d", &nc); | + | |
| - | + | ||
| - | if (nl > 20 || nc > 20) { | + | |
| - | printf("Eroare: dimensiuni > 20 \n"); | + | |
| - | return ; | + | |
| - | } | + | |
| - | | + | |
| for (i = 0; i < nl; i++) { | for (i = 0; i < nl; i++) { | ||
| for (j = 0; j < nc; j++) { | for (j = 0; j < nc; j++) { | ||
| - | scanf("%f", &a[i][j]); | + | printf("%2d", a[i][j]); |
| } | } | ||
| + | |||
| + | printf("\n"); | ||
| } | } | ||
| - | | ||
| - | /* Afisare matrice */ | ||
| - | for (i = 0; i < nl; i++) { | ||
| - | for (j = 0; j < nc; j++) { | ||
| - | printf("%f ",a[i][j]); | ||
| - | } | ||
| - | printf ("\n"); | ||
| - | } | ||
| - | | ||
| - | return 0; | ||
| } | } | ||
| </code> | </code> | ||
| - | ==== Erori comune ==== | + | Notaţia ''a[i][j]'' este interpretată astfel pentru o matrice alocată dinamic: |
| + | * ''a[i]'' conţine un pointer (o adresă ''b'') | ||
| + | * ''b[j]'' sau ''b+j'' conţine întregul din poziţia ''j'' a vectorului cu adresa ''b''. | ||
| - | * Inversarea indicilor pentru elementele unei matrice sau tablou. E usor sa-l inversezi pe i cu j in expresia A[i][j] astfel ca trebuie sa fiti atenti cand scrieti astfel de cod. Luati in considerare si folosirea de nume mai sugestive pentru variabile. | + | Astfel, ''a[i][j]'' este echivalent semantic cu expresia cu pointeri ''*(*(a + i) + j)''. |
| - | ===== Referinţe ===== | + | Totuşi, funcţia ''printmat()'' dată anterior nu poate fi apelată dintr-un program care declară argumentul efectiv ca o matrice cu dimensiuni constante. Exemplul următor este corect sintactic dar nu se execută corect: |
| - | [[http://en.wikipedia.org/wiki/Matrix_multiplication | Wikipedia - Matrix Multiplication]] | + | <code c> |
| + | int main() { | ||
| + | int x[2][2] = { {1, 2}, {3, 4} }; // O matrice patratica cu 2 linii si 2 coloane | ||
| + | printmat((int**)x, 2, 2); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| - | [[http://ocw.cs.pub.ro/courses/so/laboratoare/resurse/gdb | GDB - Tutorial]] | + | Explicaţia este interpretarea diferită a conţinutului zonei de la adresa aflată în primul argument: funcţia ''printmat()'' consideră că este adresa unui vector de pointeri (''int *a[]''), iar programul principal consideră că este adresa unui vector de vectori (''int x[][2]''), care este reprezentat liniar in memorie. |
| - | ==== Exerciții Laborator CB/CD ==== | + | Se poate defini şi o funcţie pentru alocarea de memorie la execuţie pentru o matrice. |
| - | Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul realizeaza transpunerea unei matrice patratice primite ca parametru. Se afiseaza atat matricea initiala, cat si matricea transpusa. | + | |
| - | *Nu uitati ca trebuie sa utilizam un [[http://ocw.cs.pub.ro/courses/programare/coding-style| coding style]] adecvat atunci cand scriem sursele. | + | |
| - | <code c ex1.c> | + | == Exemplu == |
| + | <code c> | ||
| + | int **newmat(int nl, int nc) { // Rezultat adresa matrice | ||
| + | int i; | ||
| + | int **p = malloc(nl * sizeof(int *)); | ||
| - | #include <stdio.h> | + | for (i = 0; i < n; i++) { |
| + | p[i] = calloc(nc, sizeof(int)); | ||
| + | } | ||
| - | #define N 100 | + | return p; |
| - | + | ||
| - | + | ||
| - | void transpose_matrix(int m[N][N], int n) | + | |
| - | { | + | |
| - | int i, j, tmp; | + | |
| - | + | ||
| - | for (i = 0; i < n; i++) { | + | |
| - | for (j = i + 1; j < n; j++) { | + | |
| - | tmp = m[i][j]; | + | |
| - | m[i][j] = m[j][i]; | + | |
| - | m[j][i] = tmp; | + | |
| - | } | + | |
| - | } | + | |
| } | } | ||
| + | </code> | ||
| + | ===== Stil de programare ===== | ||
| + | ==== Exemple de programe ==== | ||
| - | void print_matrix(int m[N][N], int n) | + | **''Exemplul 1''**: Funcţie echivalentă cu funcţia de bibliotecă ''strdup()'': |
| + | <code c> | ||
| + | // Alocare memorie si copiere sir | ||
| + | char *mystrdup(char *adr) | ||
| { | { | ||
| - | int i, j; | + | int len = strlen(adr); |
| + | char *rez = malloc(len + 1); // len+1, deoarece avem si un '\0' la final | ||
| + | |||
| + | /* Daca alocarea nu a reusit, intorcem NULL */ | ||
| + | if(rez == NULL) | ||
| + | return NULL; | ||
| - | for (i = 0; i < n; i++) { | + | strcpy(rez, adr); |
| - | for (j = 0; j < n; j++) { | + | |
| - | printf("%d ", m[i][j]); | + | return rez; |
| - | } | + | |
| - | printf("\n"); | + | |
| - | } | + | |
| - | printf("\n"); | + | |
| } | } | ||
| + | </code> | ||
| + | <code c> | ||
| + | #include <stdio.h> | ||
| + | #include <string.h> | ||
| + | #include <stdlib.h> | ||
| + | // Utilizare "mystrdup" | ||
| int main(void) | int main(void) | ||
| { | { | ||
| - | int i, n = 3; | + | char s[80], *d; |
| - | int V[N][N] = { | + | do { |
| - | {1, 2, 3}, | + | if (fgets(s, 80, stdin) == NULL) { |
| - | {4, 5, 6}, | + | break; |
| - | {7, 8, 9} | + | } |
| - | }; | + | |
| - | + | d = mystrdup(s); | |
| - | print_matrix(V, n); | + | if (d != NULL) { |
| - | transpose_matrix(V, n); | + | /* Nu s-a reusit alocarea de memorie */ |
| - | print_matrix(V, n); | + | fputs(d, stdout); |
| + | free(d); | ||
| + | } else { | ||
| + | printf("Nu s-a reusit alocarea\n"); | ||
| + | return 1; | ||
| + | } | ||
| + | } while (1); | ||
| - | return 0; | + | return 0; |
| } | } | ||
| - | |||
| </code> | </code> | ||
| - | Cerinte: | + | In exemplele urmatoare consideram ca toate alocarile de memorie nu vor esua. |
| - | *Realizati citirea unei matrice patratice de la tastatura - se va suprascrie n. | + | |
| - | *Realizati suma elementelor de deasupra diagonalei principale. | + | |
| - | *Afisati elementele de sub diagonala secundara. In locul elementelor de deasupra diagonalei secundare se va afisa 0. | + | |
| + | --- | ||
| - | **Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.** | ||
| - | [[https://drive.google.com/file/d/1u5l6ouDLRZ5WGrG8YV-iHYXqo3S6aRu9/view?usp=sharing|Checker laborator 6]] | + | **''Exemplul 3''**: Vector realocat dinamic (cu dimensiune necunoscută) |
| - | [[ https://ocw.cs.pub.ro/courses/programare/checker | Tutorial folosire checker laborator ]] | + | |
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | + | ||
| - | <hidden> | + | |
| - | Link direct către lista completă de probleme: [[https://docs.google.com/document/d/1DVe0rJnfZUHU6iOn8rh03ybG5w7Af-HB4BCx7KnJsks/edit|aici]] | + | |
| - | </hidden> | + | |
| - | + | <code c> | |
| - | ===== Exerciții de Laborator seria CB ===== | + | |
| - | + | ||
| - | - [2p] Următorul program ar trebui să citească și să afișeze o matrice. Compilați și rulați. | + | |
| - | <code c matrix.c> | + | |
| #include <stdio.h> | #include <stdio.h> | ||
| + | #include <stdlib.h> | ||
| - | #define MAX 100 | + | #define INCR 4 |
| - | void read_matrix(int a[MAX][MAX], int n) | + | int main(void) |
| { | { | ||
| - | int i, j; | + | int n, i, m; |
| + | float x, *v; | ||
| - | scanf("%d", &n); | + | n = INCR; |
| - | for (i = 0; i < n; i++) | + | i = 0; |
| - | for (j = 0; j < n; j++) | + | |
| - | scanf("%d", &a[i][j]); | + | |
| - | } | + | |
| - | int main(void) | + | v = malloc(n * sizeof(float); |
| - | { | + | |
| - | int a[MAX][MAX], n = 1, i, j; | + | |
| - | read_matrix(a, n); | + | while (scanf("%f", &x) != EOF) { |
| + | if (i == n) { | ||
| + | n = n + INCR; | ||
| + | v = realloc(v, n * sizeof(float); | ||
| + | } | ||
| + | |||
| + | v[i++] = x; | ||
| + | } | ||
| - | for(i = 0; i < n; i++) { | + | m = i; |
| - | for (j = 0; j < n; j++) | + | |
| - | printf("%d ", a[i][j]); | + | |
| - | printf("\n"); | + | |
| - | } | + | |
| - | return 0; | + | for (i = 0; i < m; i++) { |
| + | printf("%.2f ", v[i]); | ||
| + | } | ||
| + | printf("\n"); | ||
| + | |||
| + | free(v); | ||
| + | |||
| + | return 0; | ||
| } | } | ||
| </code> | </code> | ||
| - | * ce observați? | ||
| - | * folosind printf, afișați după apelul ''read_matrix'' valoarea lui ''n'' și a lui ''a[0][0]'' | ||
| - | * identificați problema | ||
| - | * rezolvați problema | ||
| - | - [3p] Următorul program inversează elementele unui vector. Compilați și rulați. | ||
| - | <code c swap.c> | ||
| - | #include <stdio.h> | ||
| - | #define MAX 100 | + | --- |
| - | void magic(int a[MAX], int n) | + | **''Exemplul 4''**: Matrice alocată dinamic (cu dimensiuni necunoscute la execuţie) |
| - | { | + | |
| - | int i; | + | |
| - | for (i = 0; i < n; i++) | + | <code c> |
| - | a[i] = a[i] & (~(0x01 & 0x42)); | + | #include <stdio.h> |
| - | + | #include <stdlib.h> | |
| - | for (i = 0; i < n/2; i++) { | + | |
| - | a[i] = a[i] ^ a[n - i]; | + | |
| - | a[n - i] = a[i] ^ a[n - i]; | + | |
| - | a[i] = a[i] ^ a[n - i]; | + | |
| - | } | + | |
| - | + | ||
| - | for (i = 0; i < n; i++); | + | |
| - | a[i] = a[i] & (~(0x42 & 0x101)); | + | |
| - | + | ||
| - | } | + | |
| int main(void) | int main(void) | ||
| { | { | ||
| - | int a[MAX], n, i; | + | int n, i, j; |
| + | int **mat; // Adresa matrice | ||
| + | |||
| + | // Citire dimensiuni matrice | ||
| + | printf("n = "); | ||
| + | scanf("%d", &n); | ||
| - | for (i = 0; i < n; i++) { | + | // Alocare memorie ptr matrice |
| - | scanf("%d", &a[i]); | + | mat = malloc(n * sizeof(int *)); |
| - | } | + | |
| - | magic(a, n); | + | for (i = 0; i < n; i++) { |
| + | mat[i] = calloc(n, sizeof(int)); | ||
| + | } | ||
| - | for (i = 0; i < n; i++); { | + | // Completare matrice |
| - | printf("%d ", a[i]); | + | for (i = 0; i < n; i++) { |
| - | } | + | for (j = 0; j < n; j++) { |
| + | mat[i][j] = n * i + j + 1; | ||
| + | } | ||
| + | } | ||
| - | return 0; | + | // Afisare matrice |
| - | } | + | for (i = 0; i < n; i++) { |
| - | </code> | + | for (j = 0;j < n; j++) { |
| - | * folosiți gdb pentru a identifica și rezolvați problemele | + | printf("%6d", mat[i][j]); |
| - | - Scrieţi un program pentru înmulţirea a două matrice, dacă aceasta este posibilă. Va trebui să implementaţi două funcţii:\\ | + | } |
| - | * [1p] una pentru citirea unei matrice într-un tablou bidimensional, dat ca parametru. | + | |
| - | * [2.5p] una care să realizeze efectiv înmulţirea a două matrice. | + | |
| - | - [1.5p] Scrieţi un program care ridică o matrice patratică cu n linii şi n coloane la puterea p, cu p număr întreg pozitiv. | + | |
| - | ===== Exerciţii de Laborator ===== | + | printf("\n"); |
| - | * **Exercitiul 1 [2 pct]:** Citiți de la tastatură o matrice (puteți hardcoda dimensiunea maximă a matricei). Afișați matricea transpusă. | + | } |
| - | <code> | + | |
| - | Exemplu: | + | |
| - | Input: 2 4 // număr de linii urmat de număr de coloane din matrice | + | return 0; |
| - | 1 2 3 4 | + | } |
| - | 5 6 7 8 | + | |
| - | + | ||
| - | Output: 1 5 | + | |
| - | 2 6 | + | |
| - | 3 7 | + | |
| - | 4 8 | + | |
| </code> | </code> | ||
| - | * **Exercitiul 2 [2 pct]:** Citiți de la tastatură 2 matrice (puteți hardcoda dimensiunea maximă a matricelor) – prima matrice numită în continuare A, iar a doua B. Determinați dacă este posibilă operația de înmulțire de matrice folosind drept operanzi cele două matrice. Determinați toate rezultatele posibile și afișati-le la consolă, precizând ordinea în care s-au înmulțit operanzii. Dacă nu se poate efectua nicio operație de înmulțire, afișați mesajul „Nu se poate efectua operatia de inmultire cu aceste matrice!”. | ||
| - | <code> | ||
| - | Exemplu: | ||
| - | Input: 2 2 // număr de linii urmat de număr de coloane din matricea A | + | --- |
| - | 1 2 | + | |
| - | 3 4 | + | |
| - | 2 2 // număr de linii urmat de număr de coloane din matricea B | + | |
| - | 5 6 | + | |
| - | 7 8 | + | |
| - | Output: A * B | + | **''Exemplul 5''**: Vector de pointeri la şiruri alocate dinamic |
| - | 19 22 | + | <code c> |
| - | 43 50 | + | /* Creare/afisare vector de pointeri la siruri */ |
| - | B * A | + | #include <stdio.h> |
| - | 23 34 | + | #include <stdlib.h> |
| - | 31 46 | + | #include <string.h> |
| - | </code> | + | |
| - | * **Exercitiul 3 [2 pct]:** Folosind logica funcției implementată anterior, implementați operația de ridicare la putere a unei matrice. Citiți de la consolă matricea, respective puterea la care trebuie ridicată matricea. Afișați la consolă rezultatul obținut sau mesajul “Operatie invalida” in caz contrar. | + | |
| - | <code> | + | |
| - | Exemplul 1: | + | |
| - | Input: 2 2 // număr de linii urmat de număr de coloane din matrice | + | // Afisare siruri reunite in vector de pointeri |
| - | 1 2 | + | void printstr(char *vp[], int n) |
| - | 3 4 | + | { |
| - | 3 // puterea la care trebuie ridicată matricea anterioară | + | int i; |
| - | Output: 37 54 | + | for (i = 0; i < n; i++) { |
| - | 81 118 | + | printf("%s\n", vp[i]); |
| + | } | ||
| + | } | ||
| - | Exemplul 2: | + | // Ordonare vector de pointeri la siruri |
| + | void sort(char *vp[], int n) | ||
| + | { | ||
| + | int i, j; | ||
| + | char *tmp; | ||
| - | Input: 2 3 // număr de linii urmat de număr de coloane din matrice | + | for (j = 1; j < n; j++) { |
| - | 1 2 3 | + | for (i = 0; i < n - 1; i++) { |
| - | 4 5 6 | + | if (strcmp(vp[i], vp[i+1]) > 0) { |
| - | 3 // puterea la care trebuie ridicată matricea anterioară | + | tmp = vp[i]; |
| + | vp[i] = vp[i+1]; | ||
| + | vp[i+1] = tmp; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| - | Output: Operatie invalida | + | // Citire siruri si creare vector de pointeri |
| - | </code> | + | int readstr (char * vp[]) |
| - | * **Exercitiul 4 [2 pct]:** Citiți o matrice pătratică de la tastatură. Intersecția diagonalelor matricei, formează 4 zone „triunghiulare” în matrice. Implementați o funcție care primește ca parametru o matrice pătratică și dimensiunea laturii acesteia și afișează elementele aflate în triunghiul superior. | + | { |
| - | <code> | + | int n = 0; |
| - | Exemplul 1: | + | char *p, sir[80]; |
| - | Input: 4 // dimensiunea laturii matricei | + | while (scanf("%s", sir) == 1) { |
| - | 1 2 3 4 | + | p = malloc(strlen(sir) + 1); |
| - | 5 6 7 8 | + | strcpy(p, sir); |
| - | 9 0 1 2 | + | vp[n] = p; |
| - | 3 4 5 6 | + | ++n; |
| + | } | ||
| - | Output: 1 2 3 4 | + | return n; |
| - | 6 7 | + | } |
| + | int main(void) | ||
| + | { | ||
| + | int n; | ||
| + | char *vp[1000]; // vector de pointeri, cu dimensiune fixa | ||
| + | n = readstr(vp); // citire siruri si creare vector | ||
| + | sort(vp, n); // ordonare vector | ||
| + | printstr(vp, n); // afisare siruri | ||
| - | + | return 0; | |
| - | + | } | |
| - | + | ||
| - | Exemplul 2: | + | |
| - | + | ||
| - | Input: 3 // dimensiunea laturii matricei | + | |
| - | 1 2 3 | + | |
| - | 4 5 6 | + | |
| - | 7 8 9 | + | |
| - | + | ||
| - | Output: 1 2 3 | + | |
| - | 5 | + | |
| </code> | </code> | ||
| - | * **Exercitiul 5 [2 pct]:** Se citesc două matrice de la tastatură. Verificați dacă a doua matrice este submatrice a primei matrice. | ||
| - | <code> | ||
| - | Exemplul 1: | ||
| - | Input: 2 2 // număr de linii urmat de număr de coloane din matricea A | + | ==== Practici recomandate ==== |
| - | 1 2 | + | |
| - | 3 4 | + | |
| - | 2 2 // număr de linii urmat de număr de coloane din matricea B | + | Deşi au fost enunţate în momentul în care au fost introduse noţiunile corespunzătoare în cursul acestui material, se pot rezuma câteva reguli importante de folosire a variabilelor de tip pointer: |
| - | 5 6 | + | * Aveţi grijă ca variabilele de tip pointer să indice către adrese de memorie valide înainte de a fi folosite; consecinţele adresării unei zone de memorie aleatoare sau nevalide (''NULL'') pot fi dintre cele mai imprevizibile. |
| - | 7 8 | + | * Utilizaţi o formatare a codului care să sugereze asocierea operatorului ''*'' cu variabila asupra căreia operează; acest lucru este în special valabil pentru declaraţiile de pointeri. |
| + | * Nu returnaţi pointeri la variabile sau tablouri definite în cadrul funcţiilor, întrucât valabilitatea acestora încetează odată cu ieşirea din corpul funcţiei. | ||
| + | * Verificaţi rezultatul funcţiilor de alocare a memoriei, chiar dacă dimensiunea pe care doriţi s-o rezervaţi este mică. Atunci când memoria nu poate fi alocată rezultatul este ''NULL'' iar programul vostru ar trebui să trateze explicit acest caz (finalizat, de obicei, prin închiderea "curată" a aplicaţiei). | ||
| + | * Nu uitaţi să eliberaţi memoria alocată dinamic, folosind funcţia ''free()''. Memoria rămasă neeliberată încetineşte performanţele sistemului şi poate conduce la erori (bug-uri) greu de depistat. | ||
| - | Output: Fals | + | ===== Studiu de caz ===== |
| - | Exemplul 2: | + | <spoiler Clase de stocare> |
| + | <note>Această secţiune este opţională şi nu este necesară pentru rezolvarea exerciţiilor de laborator, însă ajută la înţelegerea aprofundată a modului în care limbajul C lucrează cu variabilele.</note> | ||
| - | Input: 2 2 // număr de linii urmat de număr de coloane din matricea A | + | Clasa de stocare (memorare) arată când, cum şi unde se alocă memorie pentru o variabilă (vector). Orice variabilă C are o clasă de memorare care rezultă fie dintr-o declaraţie explicită, fie implicit din locul unde este definită variabila. Există trei moduri de alocare a memoriei, dar numai două corespund unor clase de memorare: |
| - | 1 2 | + | * ''Static'': memoria este alocată la compilare în segmentul de date din cadrul programului şi nu se mai poate modifica în cursul execuţiei. Variabilele externe, definite în afara funcţiilor, sunt implicit statice, dar pot fi declarate static şi variabile locale, definite în cadrul funcţiilor. |
| - | 3 4 | + | * ''Automat'': memoria este alocată automat, la activarea unei funcţii, în zona stivă alocată unui program şi este eliberată automat la terminarea funcţiei. Variabilele locale unui bloc (unei funcţii) şi argumentele formale sunt implicit din clasa ''auto''. |
| + | * ''Dinamic'': memoria se alocă la execuţie în zona ''heap'' alocată programului, dar numai la cererea explicită a programatorului, prin apelarea unor funcţii de bibliotecă (''malloc'', ''calloc'', ''realloc''). Memoria este eliberată numai la cerere, prin apelarea funcţiei ''free''. Variabilele dinamice nu au nume şi deci nu se pune problema clasei de memorare (atribut al variabilelor cu nume). | ||
| + | |||
| + | Variabilele statice pot fi iniţializate numai cu valori constante (pentru că se face la compilare), dar variabilele auto pot fi iniţializate cu rezultatul unor expresii (pentru că se face la execuţie). Toate variabilele externe (şi statice) sunt automat iniţializate cu valori zero (inclusiv vectorii). Cantitatea de memorie alocată pentru variabilele cu nume rezultă automat din tipul variabilei şi din dimensiunea declarată pentru vectori. Memoria alocată dinamic este specificată explicit ca parametru al funcţiilor de alocare. | ||
| - | 3 3 // număr de linii urmat de număr de coloane din matricea B | + | O a treia clasă de memorare este clasa ''register'' pentru variabile cărora, teoretic, li se alocă registre ale procesorului şi nu locaţii de memorie, pentru un timp de acces mai bun. În practică nici un compilator modern nu mai ţine cont de acest cuvânt cheie, folosind automat registre atunci când codul poate fi optimizat în acest fel (de exemplu când observă că nu se accesează niciodata adresa variabilei în program). |
| - | 5 6 4 | + | |
| - | 1 2 5 | + | |
| - | 3 4 6 | + | |
| - | Output: Adevarat | + | Memoria neocupată de datele statice şi de instrucţiunile unui program este împărţită între stivă şi heap. Consumul de memorie pe stivă este mai mare în programele cu funcţii recursive şi număr mare de apeluri recursive, iar consumul de memorie heap este mare în programele cu vectori şi matrice alocate (şi realocate) dinamic. |
| - | </code> | + | </spoiler> |
| - | * **Bonus [2 pct]:** CSe citește o matrice A de la tastatură. Afișați la consolă o matrice B cu proprietatea că B[i][j] = max(A[0..i][0..j]), unde max(A[0..i][0..j]) înseamnă elementul maxim din submatricea A care are colțul stânga – sus în A[0][0] și colțul dreapta – jos în A[i][j]. | + | |
| - | <code> | + | |
| - | Exemplu: | + | |
| - | Input: 3 4 // număr de linii urmat de număr de coloane din matricea A | + | ==== Exerciții ==== |
| - | 1 2 5 2 | + | Exercițiile pentru laborator se găsesc pe [[https://acs-pclp.github.io/laboratoare/06 | PCLP Laborator06: Alocarea dinamică a memoriei. Aplicații folosind tablouri]]. |
| - | 6 3 9 8 | + | |
| - | 4 0 4 5 | + | |
| - | Output: 1 2 5 5 | ||
| - | 6 6 9 9 | ||
| - | 6 6 9 9 | ||
| - | |||
| - | </code> | ||
| <hidden> | <hidden> | ||
| - | ===== Soluții ===== | + | ==== Referințe ==== |
| </hidden> | </hidden> | ||
| - | |||
| - | ===== Referinţe ===== | ||
| - | * [[https://github.com/cs-pub-ro/ComputerProgramming/blob/master/Laboratories/Lab6/Lab6.pdf | Cheatsheet matrici]] | ||
| - | |||