Responsabili:
În urma parcurgerii acestui laborator studentul va fi capabil:
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);
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ă).
char *str = malloc(30); // Aloca memorie pentru 30 de caractere int *a = malloc(n * sizeof(int)); // Aloca memorie pt. n numere intregi
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()
.
Alocarea de memorie pentru un vector şi iniţializarea zonei alocate cu zerouri se poate face cu funcţia calloc
.
int *a = calloc(n, sizeof(int)); // Aloca memorie pentru n numere intregi și inițializează zona cu zero
Codul de mai sus este perfect echivalent (dar mai rapid) cu următoarea secvenţă de instrucţiuni:
int i; int *a = malloc(n * sizeof(int)); for (i = 0; i < n; i++) { a[i] = 0; }
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.
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ă:
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
Î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:
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
.
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.
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ă.
int main(void) { int n,i; int *a; // Adresa vector alocat dinamic printf("n = "); scanf("%d", &n); // Dimensiune vector 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]); } free(a); // Nu uitam sa eliberam memoria return 0; }
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:
#define INCR 100 // cu cat creste vectorul la fiecare realocare int main(void) { int n, i, m; float x, *v, *tmp; // v = adresa vector n = INCR; i = 0; v = malloc(n * sizeof(float)); // Dimensiune initiala vector if (v == NULL) { /* Nu s-a reusit alocarea */ printf("Could not allocate v\n"); return 1; } while (scanf("%f", &x) != EOF) { if (i == n) { // Daca este necesar... n = n + INCR; // ... creste dimensiune vector tmp = realloc(v, n * sizeof(float)); if (tmp != NULL) { /* Daca s-a reusit alocarea pentru noua zona de memorie */ v = tmp; } else { /* Daca nu s-a reusit alocarea */ break; } } v[i++] = x; // Memorare in vector numar citit } m = i; for (i = 0; i < m; i++) { // Afisare vector printf("%f ", v[i]); } printf("\n"); free(v); return 0; }
Alocarea dinamică pentru o matrice este importantă deoarece:
ragged arrays
, datorită formelor “zimţate” din reprezentările grafice) 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.
int main (void) { int **a; int i, j, nl, nc; printf("nr. linii = "); scanf(“%d”, &nl); printf("nr. coloane = "); scanf(“%d”, &nc); /* * In cele ce urmeaza presupunem ca toate apelurile de alocare de memorie * nu vor esua. */ 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 } // Completare diagonala matrice unitate for (i = 0; i < nl; i++) { a[i][i] = 1; // a[i][j]=0 pentru i != j // Afisare matrice printmat(a, nl, nc); for (i = 0; i < nl; i++) free(a[i]); free(a); // Nu uitam sa eliberam! return 0; }
Funcţia de afişare a matricei se poate defini astfel:
void printmat(int **a, int nl, int nc) { for (i = 0; i < nl; i++) { for (j = 0; j < nc; j++) { printf("%2d", a[i][j]); } printf("\n"); } }
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
.
Astfel, a[i][j]
este echivalent semantic cu expresia cu pointeri *(*(a + i) + j)
.
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:
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; }
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.
Se poate defini şi o funcţie pentru alocarea de memorie la execuţie pentru o matrice.
int **newmat(int nl, int nc) { // Rezultat adresa matrice int i; int **p = malloc(nl * sizeof(int *)); for (i = 0; i < n; i++) { p[i] = calloc(nc, sizeof(int)); } return p; }
Exemplul 1
: Funcţie echivalentă cu funcţia de bibliotecă strdup()
:
// Alocare memorie si copiere sir char *mystrdup(char *adr) { 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; strcpy(rez, adr); return rez; }
#include <stdio.h> #include <string.h> #include <stdlib.h> // Utilizare "mystrdup" int main(void) { char s[80], *d; do { if (fgets(s, 80, stdin) == NULL) { break; } d = mystrdup(s); if (d != NULL) { /* Nu s-a reusit alocarea de memorie */ fputs(d, stdout); free(d); } else { printf("Nu s-a reusit alocarea\n"); return 1; } } while (1); return 0; }
Puteti testa codul aici.
In exemplele urmatoare consideram ca toate alocarile de memorie nu vor esua.
Exemplul 3
: Vector realocat dinamic (cu dimensiune necunoscută)
#include <stdio.h> #include <stdlib.h> #define INCR 4 int main(void) { int n, i, m; float x, *v; n = INCR; i = 0; v = malloc(n * sizeof(float); while (scanf("%f", &x) != EOF) { if (i == n) { n = n + INCR; v = realloc(v, n * sizeof(float); } v[i++] = x; } m = i; for (i = 0; i < m; i++) { printf("%.2f ", v[i]); } printf("\n"); free(v); return 0; }
Puteti testa codul aici.
Exemplul 4
: Matrice alocată dinamic (cu dimensiuni necunoscute la execuţie)
#include <stdio.h> #include <stdlib.h> int main(void) { int n, i, j; int **mat; // Adresa matrice // Citire dimensiuni matrice printf("n = "); scanf("%d", &n); // Alocare memorie ptr matrice mat = malloc(n * sizeof(int *)); for (i = 0; i < n; i++) { mat[i] = calloc(n, sizeof(int)); } // Completare matrice for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { mat[i][j] = n * i + j + 1; } } // Afisare matrice for (i = 0; i < n; i++) { for (j = 0;j < n; j++) { printf("%6d", mat[i][j]); } printf("\n"); } return 0; }
Puteti testa codul aici.
Exemplul 5
: Vector de pointeri la şiruri alocate dinamic
/* Creare/afisare vector de pointeri la siruri */ #include <stdio.h> #include <stdlib.h> #include <string.h> // Afisare siruri reunite in vector de pointeri void printstr(char *vp[], int n) { int i; for (i = 0; i < n; i++) { printf("%s\n", vp[i]); } } // Ordonare vector de pointeri la siruri void sort(char *vp[], int n) { int i, j; char *tmp; for (j = 1; j < n; j++) { for (i = 0; i < n - 1; i++) { if (strcmp(vp[i], vp[i+1]) > 0) { tmp = vp[i]; vp[i] = vp[i+1]; vp[i+1] = tmp; } } } } // Citire siruri si creare vector de pointeri int readstr (char * vp[]) { int n = 0; char *p, sir[80]; while (scanf("%s", sir) == 1) { p = malloc(strlen(sir) + 1); strcpy(p, sir); vp[n] = p; ++n; } return n; } 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; }
Puteti testa codul aici.
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:
NULL
) pot fi dintre cele mai imprevizibile. *
cu variabila asupra căreia operează; acest lucru este în special valabil pentru declaraţiile de pointeri. NULL
iar programul vostru ar trebui să trateze explicit acest caz (finalizat, de obicei, prin închiderea “curată” a aplicaţiei). free()
. Memoria rămasă neeliberată încetineşte performanţele sistemului şi poate conduce la erori (bug-uri) greu de depistat.Codul sursa se gaseste aici
Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul citeste o matrice si afiseaza suma elementelor de pe fiecare linie.
Cerinte:
Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.
char *build_number(int value);
char *add_numbers(char *a, char *b);
char *multiply_numbers(char *a, char *b);