brackets-curlyGestionarea memoriei

Memoria este una dintre resursele fundamentale pe care orice program le utilizează. În C++, înțelegerea modului în care funcționează memoria poate face diferența dintre un program eficient și unul plin de bug-uri sau cu o performanță nesatisfăcătoare. Când programul tău rulează, computerul îi alocă spațiu în memoria RAM - imaginează-ți memoria ca pe un dulap cu multe sertare (bytes). În acest "dulap", programul poate stoca variabile, structuri de date și tot ce are nevoie pentru a funcționa. Fiecare variabilă poate ocupa unul sau mai multe "sertare".

Pentru a eficientiza accesul la aceste valori, memoria gestionată în cadrul unui program C++ se împarte în două zone principale: stack și heap.

Stack-ul este o zonă de memorie organizată și gestionată automat, care funcționează după principiul LIFO (Last In, First Out) - ultimul element adăugat este primul element procesat. Aici sunt stocate variabile locale din funcții, parametrii funcțiilor, adrese de returnare și alte elemente alocate static. Este important de reținut că memoria stack este limitată și nu este concepută pentru a gestiona volume mari de date. Exemplul de cod de mai jos alocă trei variabile în mod static, în stack.

#include <iostream>
int main() {
    int x = 10;
    double y = 3.14;
    char c = 'A';
    
    std::cout<< x << " " << y << " " << c;
    
    return 0;
}

În cazul programului de mai sus, memoria stack arată astfel:

În momentul în care funcția main începe execuția, în memoria stack se adaugă o înregistrare care specifică faptul că, din acel punct, toate intrările din stivă aparțin funcției main. Ulterior, adăugarea de variabile noi se execută peste această intrare și vor fi disponibile atâta timp cât funcția main încă nu și-a terminat execuția. La întâlnirea instrucțiunii return 0, memoria stack este golită automat, de sus în jos, până la întâlnirea intrării aferentă stack frame-ului curent.

Heap-ul este o zonă de memorie neorganizată, unde datele sunt alocate "la grămadă" - de unde și numele de heap. Alocarea și dealocarea zonelor de memorie heap revine exclusiv în sarcina programatorului și este important ca acestea să fie făcute corect și complet. Deși prelucrarea datelor din heap este mult mai lentă decât a celor din stack, avantajele principale în folosirea acestei zone sunt reprezentate de capacitatea de stocare mult mai mare, precum și posibilitatea de a permite obiectelor să "supraviețuiască" în afara funcțiilor care le-au declarat. Blocul de cod de mai jos alocă dinamic o variabilă și prelucrează valoarea ei. Totuși, exemplul este incomplet!

Memoria stack și heap în urma execuției programului poate fi reprezentată astfel:

Observăm apariția a două simboluri noi. Aceste simboluri se numesc operatori și au o mare importanță în accesarea corectă a variabilelor alocate dinamic.

  • & - operatorul de adresare. Acesta ne spune unde se află variabila în memorie. Returnează adresa RAM a variabilei respective (în cazul nostru, x00112233)

  • * - operatorul de dereferențiere. Acesta ne spune ce valoare se află în acea variabilă. Returnează valoarea efectivă a variabilei (în cazul nostru, 50).

Important: variabilele alocate în heap nu sunt identificate după nume, ci doar după adresa lor. Din acest motiv, pentru orice obiect alocat dinamic în heap, se reține în stack o variabilă corespondentă care face posibilă legătura cu această zonă de memorie. Această variabilă specială poartă numele de pointer.

Și în cazul memoriei alocate dinamic, stack-ul funcționează după același principiu: la apelarea funcției main se generează o intrare pentru un stack frame, se alocă (tot pe stack) memoria pentru pointer, iar la intâlnirea instrucțiunii de returnare, se elimină, de sus în jos, tot conținutul stack-ului până la întâlnirea intrării aferente stack frame-ului. Observăm, însă, o problemă!

După încheierea execuției funcției main și resetarea stack-ului până la cel mai apropiat stack frame, pointer-ul care reținea adresa variabilei alocate în heap a fost și el eliminat, ceea ce înseamnă că zona de memorie alocată inițial pentru a reține valoarea 50 nu mai este accesibilă prin pointer. Totodată, ea nu are un nume propriu pentru a putea face posibilă identificarea, dar nici nu a fost eliminată automat, deoarece memoria heap nu beneficiază de acest lucru. Așadar, am ajuns în situația în care avem o variabilă alocată în memorie, dar pentru care nu există nicio modalitate prin care să poată fi accesată. Acest tip de problemă poartă numele de memory leak.

Rezolvarea acestei probleme presupune gestionarea manuală a memoriei aferente acelei variabile.

Prin introducerea instrucțiunii delete x, compilatorul va executa dealocarea memoriei heap aferente pointerului x (atenție, nu îl șterge pe x din stack, ci zona de memorie heap stocată la adresa din x) înainte de întâlnirea instrucțiunii de returnare și golirea stack-ului până la intrarea aferentă stack frame-ului.

Last updated