Alocarea memoriei în limbajul C

Clase de memorare (alocare a memoriei) în C

Clasa de memorare arată când, cum şi unde se alocă memorie pentru o variabilă.

Orice variabilă are o clasă de memorare care rezultă fie din declaraţia ei, fie implicit din locul unde este definită variabila.

Zona de memorie utilizată de un program C cuprinde 4 subzone:

  • Zona text: în care este păstrat codul programului
  • Zona de date: în care sunt alocate (păstrate) variabilele globale
  • Zona stivă: în care sunt alocate datele temporare (variabilele locale)
  • Zona heap: în care se fac alocările dinamice de memorie.

Moduri de alocare a memoriei:

  • Statică: variabile implementate în zona de date – globale 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.
  • Auto: variabile implementate în stivă – locale 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 parametrii formali sunt implicit din clasa auto. Memoria se alocă în stiva ataşată programului.
  • Dinamică: variabile implementate în heap Memoria se alocă dinamic (la execuţie) în zona heap ataşată 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
  • Register: variabile implementate într-un registru de memorie

 

Clase de alocare a memoriei: Auto

Variabilele locale unui bloc (unei funcţii) şi parametrii formali sunt implicit din clasa auto.

Durata de viaţă a acestor variabile este temporară: memoria este alocată automat, la activarea blocului/funcţiei, în zona stivă alocată programului şi este eliberată automat la ieşirea din bloc/terminarea funcţiei. Variabilele locale NU sunt iniţializate! Trebuie să le atribuim o valoare iniţială!

Exemplu:

int doi() {

int x = 2;

return x;

}

int main() {

int a; {

int b = 5;

a = b*doi();

}

printf(“a = %d\n”, a);

return 0;

}

Conţinut stivă:

(x) 2

(b) 5

(a) 10

Clase de alocare a memoriei: Static

Memoria este alocată la compilare în segmentul de date din cadrul programului şi nu se mai poate modifica în cursul execuţiei.

Variabilele globale sunt implicit statice (din clasa static).

Pot fi declarate static şi variabile locale, definite în cadrul funcţiilor, folosind cuvântul cheie static.

O variabilă sau o funcţie declarată (sau implicit) static are durata de viaţă egală cu cea a programului. In consecinţă, o variabilă statică declarată într-o funcţie îşi păstrează valoarea între apeluri succesive ale funcţiei, spre deosebire de variabilele auto care sunt realocate pe stivă la fiecare apel al funcţiei şi pornesc de fiecare dată cu valoarea primită la iniţializarea lor (sau cu o valoare imprevizibilă, dacă nu sunt iniţializate).

Exemple:

int f1() { int x = 1; /*Variabilă locală, iniţializată cu 1 la fiecare apel al lui f1*/ ……

}

int f2() {

static int y = 99; /*Variabilă locală statică, iniţializată cu 99 doar la primul apel al lui f2; valoarea ei este reţinută pe parcursul apelurilor lui f2*/ ……

}

int f() {

static int nr_apeluri=0;

nr_apeluri++;

printf(“funcţia f() este apelata pentru a %d-a oara\n“, nr_apeluri);

return nr_apeluri;

}

int main() {

int i;

for (i=0; i<10; i++) f(); //f() apelata de 10 ori

printf(“functia f() a fost apelata de %d ori.”, f()); // 11 ori!!

return 0;

}

Clase de alocare a memoriei: Register

A treia clasă de memorare este clasa register, pentru variabile cărora li se alocă registre ale procesorului şi nu locaţii de memorie, pentru un timp de acces mai bun.

O variabilă declarată register solicită sistemului alocarea ei într-un registru maşină, dacă este posibil.

De obicei compilatorul ia automat decizia de alocare a registrelor maşinii pentru anumite variabile auto din funcţii. Se utilizează pentru variabile “foarte solicitate”, pentru mărirea vitezei de execuţie.

Exemplu:

{ register int i;

for(i = 0; i < N; ++i){

/*… */

}

} /* se elibereaza registrul */

 

Clase de alocare a memoriei: extern

O variabilă externă este o variabilă definită în alt fişier. Declaraţia extern îi spune compilatorului că identificatorul este definit în alt fişier sursă (extern). Ea este este alocată în funcţie de modul de declarare din fişierul sursă.

Exemplu: // File1.cpp

extern int i; // Declara aceasta variabila ca fiind definita in alt fisier //

File2.cpp

int i = 88; // Definit aici

Alocarea dinamică a memoriei

Reamintim că pentru variabilele alocate dinamic memoria se alocă dinamic (la execuţie) în zona heap ataşată 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.

Principalele diferenţe între alocarea statică şi cea dinamică sunt:

  • La alocarea statică, compilatorul alocă şi eliberează memoria automat, ocupându-se astfel de gestiunea memoriei, în timp ce la alocarea dinamică programatorul este cel care gestionează memoria, având un control deplin asupra adreselor de memorie şi a conţinutului lor.
  • Entităţile alocate static sau auto sunt manipulate prin intermediul unor variabile, în timp ce cele alocate dinamic sunt gestionate prin intermediul pointerilor!

 

Funcţii standard pentru alocarea dinamică a memoriei Funcţiile standard pentru alocarea dinamica a memoriei sunt declarate în fişierele stdlib.h şi alloc.h.

Alocarea memoriei:

void *malloc(size_t size);

Alocă memorie de dimensiunea size octeţi

void *calloc(int nitems, size_t size);

Alocă memorie pentru nitems de dimensiune size octeţi şi iniţializează zona alocată cu zerouri

Cele două funcţii au ca rezultat adresa zonei de memorie alocate (de tip void. Dacă cererea de alocare nu poate fi satisfăcută, pentru că nu mai exista un bloc continuu de dimensiunea solicitată, atunci funcţiile de alocare au rezultat NULL. Funcţiile de alocare au rezultat void* deoarece funcţia nu ştie tipul datelor ce vor fi memorate la adresa respectivă.

La apelarea funcţiilor de alocare se folosesc:

  • Operatorul sizeof pentru a determina numărul de octeţi necesar unui tip de date (variabile);
  • Operatorul de conversie cast pentru adaptarea adresei primite de la funcţie la tipul datelor memorate la adresa respectivă (conversie necesară atribuirii între pointeri de tipuri diferite).

Exemple:

//aloca memorie pentru 30 de caractere:

char * str = (char*) malloc(30);

//aloca memorie ptr. n întregi:

int * a = (int *) malloc( n * sizeof(int));

//aloca memorie ptr. n întregi si initializeaza cu zerouri

int * a= (int*) calloc (n, sizeof(int) );

 

Realocarea 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ă:

void *realloc(void* adr, size_t size);

Funcţia realloc realizează următoarele operaţii:

  • Alocă o zonă de dimensiunea specificată prin al doilea parametru.
  • Copiază la noua adresă datele de la adresa veche (primul parametru).
  • Eliberează memoria de la adresa veche.

Exemple:

// dublare dimensiune curenta a zonei de la adr. a

a = (int *)realloc (a, 2*n* sizeof(int));

 

Atenţie! Se va evita redimensionarea unui vector cu o valoare foarte mică de un număr mare de ori; o strategie de realocare folosită pentru vectori este dublarea capacităţii lor anterioare.

Exemplu de funcţie cu efectul funcţiei realloc, dar doar pentru caractere:

char * ralloc (char * p, int size) { // p = adresa veche

char *q; // q=adresa noua

if (size==0) { // echivalent cu free free(p);

return NULL;

}

q = (char*) malloc(size); // aloca memorie

if (q) { // daca alocare reusita

memcpy(q,p,size); // copiere date de la p la q

free(p); // elibereaza adresa p

}

return q; // q poate fi NULL

}

 

Eliberarea memoriei

Funcţia free are ca argument o adresă (un pointer) şi eliberează zona de la adresa respectivă (alocată dinamic). Dimensiunea zonei nu mai trebuie specificată deoarece este memorată la începutul zonei alocate (de către funcţia de alocare):

void free(void* adr);

Eliberarea memoriei prin free este inutilă la terminarea unui program, deoarece înainte de încărcarea şi lansarea în execuţie a unui nou program se eliberează automat toată memoria heap.

Exemple:

char *str;

str=(char *)malloc(10*sizeof(char));

str=(char *)realloc(str,20*sizeof(char));

free(str);

Vectori alocaţi dinamic

Structura de vector are avantajul simplitătii şi economiei de memorie faţă de alte structuri de date folosite pentru memorarea unei colecţii de date.

Dezavantajul unui vector cu dimensiune fixă (stabilită la declararea vectorului şi care nu mai poate fi modificată la execuţie) apare în aplicaţiile cu vectori de dimensiuni foarte variabile, în care este dificil de estimat o dimensiune maximă, fără a face risipă de memorie.

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 soluţie 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ă si mai mult utilizarea unor vectori cu dimensiune variabilă.

Un vector alocat dinamic se declară ca variabilă pointer care se iniţializează cu rezultatul funcţiei de alocare. Tipul variabilei pointer este determinat de tipul componentelor vectorului.

Exemplu:

#include<stdlib.h>

#include<studio.h>

int main() {

int n, i;

int * a;

// adresa vector alocat dinamic

printf (“n=”);

scanf (“%d”, &n);

// dimensiune vector a=(int *) calloc (n,sizeof(int));

// aloca memorie pentru vector

// sau: a=(int*) malloc (n*sizeof(int));

// citire component vector:

printf (“componente vector: \n”);

for (i=0;i<n;i++)

scanf(“%d”, &a[i]);

// afisare vector:

for (i=0;i<n;i++)

printf(“%d”, &a[i]);

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. În acest caz se poate recurge la o realocare dinamică a memoriei.O strategie de realocare pentru vectori este dublarea capacităţii lor anterioare.

În exemplul următor se citeşte un număr necunoscut de valori întregi într-un vector extensibil: Program care citeşte numere reale până la CTRL+Z, le memorează într-un vector alocat şi realocat dinamic în funcţie de necesităţi şi le afişează.

Rezolvare:

#include<stdlib.h>

#include<studio.h>

#define INCR 4

int main() {

int n,n_crt,i ;

float x, * v;

n = INCR;

// dimensiune memorie alocata

n_crt = 0;

// numar curent elemente în vector

v = (float *)malloc (n*sizeof(float));

//alocare initiala

while (scanf(“%f”,&x) !=EOF){

if (n_crt == n) {

n = n + INCR;

v = (float *) realloc (v, n*sizeof(float) ); //realocare

}

v[n_crt++] = x;

}

for (i=0; i<n crt ; i++)

printf(“%.2f”, v[i]);

return 0;

}

Matrice alocate dinamic

Alocarea dinamică pentru o matrice este importantă deoarece foloseşte economic memoria şi permite matrice cu linii de lungimi diferite. De asemenea reprezintă o soluţie bună la problema parametrilor de funcţii de tip matrice.

Exemplu de declarare matrice de întregi:

int * a[M]; // M este o constanta simbolica

Dacă nu se poate estima numărul de linii din matrice atunci şi vectorul de pointeri se alocă dinamic, iar declararea matricei se face ca pointer la pointer:

int** a;

În acest caz se va aloca mai întâi memorie pentru un vector de pointeri (funcţie de numărul liniilor) şi apoi se va aloca memorie pentru fiecare linie cu memorarea adreselor liniilor în vectorul de pointeri.

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.

 

Funcţii cu rezultat vector

O funcţie nu poate avea ca rezultat un vector sub forma:

int [] funcţie(…) {…}

O funcţie poate avea ca rezultat doar un pointer !! int *funcţie(…) {…} De obicei, rezultatul pointer este egal cu unul din argumente, eventual modificat în funcţie.

Exemplu corect:

// incrementare pointer p

char * incptr ( char * p) {

return ++p;

}

Atenţie!

Acest pointer nu trebuie să conţină adresa unei variabile locale, deoarece:

  • O variabilă locală are o existenţă temporară, garantată numai pe durata executării funcţiei în care este definită (cu excepţia variabilelor locale statice)
  • Adresa unei astfel de variabile nu trebuie transmisă în afara funcţiei, pentru a fi folosită ulterior!!

 

Exemplu greşit:

// vector cu cifrele unui nr intreg de maxim cinci cifre

int * cifre (int n) {

int k, c[5]; // vector local

for (k=4;k>=0;k–) {

c[k]=n%10;

n=n/10;

}

return c; // aici este eroarea !

}

//warning la compilare şi POSIBIL rezultate greşite în main!!

O funcţie care trebuie să transmită ca rezultat un vector poate fi scrisă corect în în mai multe feluri:

1. Primeşte ca parametru adresa vectorului (definit şi alocat în altă funcţie) şi depune rezultatele la adresa primită (este soluţia recomandată!!)

2. Alocă dinamic memoria pentru vector (cu “malloc”)

  • Această alocare (pe heap) se menţine şi la ieşirea din funcţie.
  • Funcţia are ca rezultat adresa vectorului alocat în cadrul funcţiei.
  • Problema este unde şi când se eliberează memoria alocată.

3. O soluţie oarecum echivalentă este utilizarea unui vector local static, care continuă să existe şi după terminarea funcţiei.

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s