LABORATORUL 09

Alocare dinamică de memorie

Durată: 2 ore Bibliotecă: stdlib.h Precedent: Laboratorul 8 PDF îndrumar EN English version

Până acum, dimensiunea datelor era fixată la scrierea programului. Alocarea dinamică permite stabilirea ei în timpul execuției, cerând memorie exact atunci când este nevoie și eliberând-o când nu mai este utilă.

1Obiectivele lucrării

  • Distingerea alocării statice, automate și dinamice
  • Utilizarea funcțiilor malloc, calloc, realloc și free
  • Verificarea obligatorie a reușitei alocării
  • Identificarea și prevenirea pierderilor de memorie
  • Folosirea operatorilor new și delete din C++

2De ce este necesară alocarea dinamică

La declararea unui tablou, dimensiunea trebuie să fie o constantă cunoscută la compilare. Aceasta duce la două probleme opuse:

SituațieConsecință
Se declară un tablou mult mai mare decât e nevoiememorie ocupată inutil
Numărul real de elemente depășește dimensiunea declaratătrebuie modificat codul și recompilat
SoluțiaSe stabilește dimensiunea în momentul execuției, când numărul de elemente este cunoscut, se alocă exact atât cât trebuie, iar la final memoria se eliberează pentru a putea fi refolosită în alt scop.

3Segmentele de memorie

Memoria unui program este împărțită în zone specializate, adresate prin regiștri de segment (CS, DS, ES, SS):

ZonăCe conțineCând se alocăCând se eliberează
Segment de cod (CS)instrucțiunile executabilela încărcarea programuluila terminare
Segment de date (DS)variabile globale și staticela încărcarela terminare
Stivă (SS)variabile locale, parametri, adrese de revenirela intrarea în blocautomat, la ieșire
Heapdate alocate dinamicla apelul malloc / newmanual, prin free / delete
Segment de cod - instrucțiuni Date statice - globale, static HEAP - alocare dinamică crește în sus ↑ (malloc / new) crește în jos ↓ (apeluri de funcții) STIVĂ - variabile locale adrese mici adrese mari alocare manuală alocare automată
Fig. 1 - Organizarea memoriei unui program în execuție

4Funcțiile de alocare din C

FuncțiePrototipEfect
mallocvoid* malloc(size_t n)alocă n octeți, neinițializați
callocvoid* calloc(size_t nr, size_t dim)alocă nr×dim octeți, puși pe zero
reallocvoid* realloc(void* p, size_t n)redimensionează o zonă deja alocată
freevoid free(void* p)eliberează zona indicată
tiparul corect de utilizare
int *v = (int *)malloc(n * sizeof(int));   // 1. alocare

if (v == NULL) {                            // 2. VERIFICARE obligatorie
    printf("Memorie insuficienta!\n");
    return 1;
}

/* ... utilizare ... */                     // 3. folosire

free(v);                                    // 4. eliberare
v = NULL;                                   // 5. evita folosirea accidentala
Verificarea nu este opționalăDacă sistemul nu poate satisface cererea, malloc returnează NULL. Folosirea rezultatului fără verificare duce la dereferențierea unui pointer nul și oprirea brutală a programului.
realloc - atenție la pointerul returnatFuncția poate muta blocul la altă adresă. Nu scrieți niciodată v = realloc(v, ...) direct: dacă alocarea eșuează, se returnează NULL, iar adresa veche se pierde definitiv. Folosiți o variabilă temporară.

5Simulator: pointer către zona alocată

Selectați ținta pentru a vedea diferența dintre un pointer valid, unul care indică spre altă zonă și unul pus pe NULL după eliberare.

Pointer, heap și starea NULL
De ce se pune pointerul pe NULL după freeDupă eliberare, pointerul păstrează adresa veche, dar zona nu mai este a noastră. Un free repetat pe aceeași adresă produce coruperea structurilor interne ale alocatorului. Atribuirea v = NULL transformă o eroare subtilă într-una imediat vizibilă.

6Operatorii new și delete din C++

OperațieCC++
Alocare pentru o valoarep = (int*)malloc(sizeof(int));p = new int;
Alocare cu inițializarealocare + atribuire separatăp = new int(42);
Alocare pentru un tablouv = (int*)malloc(n*sizeof(int));v = new int[n];
Eliberare valoarefree(p);delete p;
Eliberare tabloufree(v);delete[] v;
Nu amestecați cele două mecanismeMemoria obținută cu malloc se eliberează cu free; cea obținută cu new, cu delete. Pentru tablouri se folosește obligatoriu delete[] - altfel destructorii elementelor nu se apelează.
Avantajele lui newNu necesită conversie explicită de tip, calculează singur dimensiunea, apelează constructorii pentru obiecte și, la eșec, generează o excepție în loc să returneze NULL în tăcere.

7Erori clasice

EroareCauzăEfect
Memory leaklipsa freeconsum crescător de memorie; programele lungi ajung să epuizeze sistemul
Double freeeliberare de două oricorupere internă a alocatorului, oprire imprevizibilă
Use after freeacces după eliberaredate aleatoare sau blocare; vulnerabilitate de securitate
Buffer overflowscriere peste dimensiunea alocatăsuprascrierea structurilor vecine din heap
Alocare neverificatălipsa testului == NULLdereferențierea unui pointer nul
Regula de aurFiecare malloc trebuie să aibă exact un free corespunzător, iar fiecare new exact un delete. Scrieți-le pereche, chiar în momentul alocării, ca să nu le uitați.

8Cod sursă

vector_dinamic.c - dimensiune stabilită la execuție
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n;

    printf("Cate elemente? ");
    if (scanf("%d", &n) != 1 || n <= 0) {
        printf("Valoare invalida\n");
        return 1;
    }

    int *v = (int *)malloc(n * sizeof(int));   // alocare exacta
    if (v == NULL) {                            // verificare obligatorie
        printf("Alocare esuata!\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        printf("v[%d] = ", i);
        scanf("%d", &v[i]);
    }

    long suma = 0;
    for (int i = 0; i < n; i++) suma += v[i];

    printf("\nSuma = %ld, media = %.3f\n", suma, (double)suma / n);

    free(v);        // eliberare
    v = NULL;       // protectie impotriva folosirii accidentale

    return 0;
}
redimensionare.c - realloc folosit corect
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int capacitate = 4, nr = 0;
    int *v = (int *)malloc(capacitate * sizeof(int));
    if (!v) return 1;

    printf("Introduceti numere (0 = stop):\n");

    int x;
    while (scanf("%d", &x) == 1 && x != 0) {

        if (nr == capacitate) {                 // s-a umplut: dublam capacitatea
            capacitate *= 2;

            int *temp = (int *)realloc(v, capacitate * sizeof(int));
            if (temp == NULL) {                 // NU atribuim direct lui v!
                printf("Redimensionare esuata\n");
                free(v);
                return 1;
            }
            v = temp;
            printf("  [capacitate marita la %d]\n", capacitate);
        }
        v[nr++] = x;
    }

    printf("\nAti introdus %d valori: ", nr);
    for (int i = 0; i < nr; i++) printf("%d ", v[i]);
    printf("\n");

    free(v);
    return 0;
}
matrice_dinamica.c - tablou bidimensional alocat dinamic
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int linii, coloane;

    printf("Linii si coloane: ");
    scanf("%d %d", &linii, &coloane);

    // 1. tablou de pointeri catre linii
    int **m = (int **)malloc(linii * sizeof(int *));
    if (!m) return 1;

    // 2. cate o linie pentru fiecare pointer
    for (int i = 0; i < linii; i++) {
        m[i] = (int *)malloc(coloane * sizeof(int));
        if (!m[i]) {                          // eliberam ce am alocat deja
            for (int k = 0; k < i; k++) free(m[k]);
            free(m);
            return 1;
        }
    }

    for (int i = 0; i < linii; i++)
        for (int j = 0; j < coloane; j++)
            m[i][j] = (i + 1) * (j + 1);      // tabla inmultirii

    for (int i = 0; i < linii; i++) {
        for (int j = 0; j < coloane; j++) printf("%5d", m[i][j]);
        printf("\n");
    }

    // eliberare in ordine INVERSA alocarii
    for (int i = 0; i < linii; i++) free(m[i]);
    free(m);

    return 0;
}
new_delete.cpp - varianta C++
#include <iostream>
using namespace std;

int main()
{
    int n;
    cout << "Cate elemente? ";
    cin >> n;

    int *v = new int[n];              // fara conversie, fara sizeof

    for (int i = 0; i < n; i++) v[i] = (i + 1) * (i + 1);

    for (int i = 0; i < n; i++) cout << v[i] << " ";
    cout << endl;

    delete[] v;                       // OBLIGATORIU cu paranteze patrate

    // alocare cu initializare
    int *p = new int(42);
    cout << "*p = " << *p << endl;
    delete p;                         // fara paranteze, pentru o singura valoare

    return 0;
}

9Atelier de cod

În modul Pas cu pas, panoul din dreapta are o secțiune heap: acolo apar blocurile alocate cu malloc și se vede exact când devin „eliberate”.

Un vector a cărui dimensiune se află la execuție
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n, i, *v;

    printf("Cate elemente? ");
    scanf("%d", &n);

    v = (int*) malloc(n * sizeof(int));
    if (v == NULL) {
        printf("Alocare esuata\n");
        return 1;
    }

    for (i = 0; i < n; i++) v[i] = (i + 1) * (i + 1);

    printf("Patratele primelor %d numere: ", n);
    for (i = 0; i < n; i++) printf("%d ", v[i]);
    printf("\n");

    free(v);
    return 0;
}
De încercatȘtergeți linia free(v); și rulați din nou: programul funcționează la fel. Tocmai de aceea scurgerile de memorie sunt periculoase - nu se văd. Acum mutați free(v); înainte de bucla de afișare și veți primi o eroare clară.
Exercițiu - realloc și eliberare corectă
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *v, i;

    v = (int*) malloc(3 * sizeof(int));
    for (i = 0; i < 3; i++) v[i] = i + 1;

    /* Mariti zona la 6 elemente folosind realloc,
       apoi completati elementele 3, 4, 5 cu valorile 4, 5, 6.
       Nu uitati sa verificati rezultatul lui realloc. */

    for (i = 0; i < 6; i++) printf("%d ", v[i]);
    printf("\n");

    free(v);
    return 0;
}

10Sarcini de lucru

  • Alocați dinamic un vector de dimensiune citită de la tastatură și calculați suma și media.
  • Verificați ce returnează malloc pentru o cerere absurd de mare (de exemplu 10¹⁰ octeți).
  • Comparați conținutul zonelor obținute cu malloc și cu calloc, imediat după alocare.
  • Implementați un vector care își dublează capacitatea prin realloc când se umple.
  • Alocați dinamic o matrice și eliberați-o corect, în ordinea inversă alocării.
  • Omiteți intenționat free într-o buclă și observați creșterea memoriei în Task Manager.
  • Rescrieți unul dintre programe în C++, folosind new și delete[].

11Aplicație de aprofundare

ExtindereImplementați o listă simplu înlănțuită de numere întregi, cu alocare dinamică pentru fiecare nod: adăugare la început și la sfârșit, ștergerea unui element după valoare, afișarea listei și eliberarea completă a memoriei. Verificați cu un contor global că numărul de apeluri malloc este egal cu numărul de apeluri free la terminarea programului.

12Întrebări de verificare

13Resurse