Linguaggio C++

Memoria Dinamica

Memoria dinamica in C++: differenze tra stack e heap, uso di new/new[] e delete/delete[], puntatori nulli, leak e dangling pointer, con esempi e laboratorio guidato.

La memoria dinamica permette di creare (e distruggere) variabili e array a runtime, quando non conosciamo a priori quanti dati serviranno. In C++ la memoria dinamica si gestisce con gli operatori new e delete (e le loro varianti per gli array new[] e delete[]).


1️⃣ Stack vs Heap (perché serve new)

  • Stack: memoria automatica. Le variabili locali esistono solo finché la funzione non termina. Veloce, ma dimensione e durata sono fisse.
  • Heap: memoria dinamica. Crei oggetti quando ti servono e li distruggi quando hai finito. Flessibile, ma devi gestire tu la liberazione.
Esempio rapido:
- Stack:  int a = 5;        // si distrugge da sola a fine blocco
- Heap:   int* p = new int; // vive finché non fai delete p;

2️⃣ new e delete (variabile singola)

new Tipo alloca sullo heap un oggetto di tipo Tipo e restituisce un puntatore ad esso. delete libera la memoria precedentemente allocata con new.

#include <iostream>
using namespace std;

int main() {
    int* p = new int;   // alloca un int non inizializzato
    *p = 42;            // lo uso tramite dereferenziazione
    cout << *p << endl; // 42
    delete p;           // libera la memoria
    p = nullptr;        // azzera il puntatore (buona pratica)
}

Inizializzazione diretta al new:

int* q = new int(7);     // inizializza a 7
double* d = new double{3.14}; // lista di inizializzazione
delete q;
delete d;

3️⃣ new[] e delete[] (array dinamici)

Per un numero di elementi deciso a runtime si usa new[] e si libera con delete[]:

int n;
cin >> n;
int* v = new int[n];      // array di n interi su heap

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

delete[] v;               // IMPORTANTE: delete[] per gli array
v = nullptr;

4️⃣ Struct e memoria dinamica

Puoi dinamicamente creare anche struct (e array di struct). Si usa l operatore freccia -> per accedere ai campi.

struct Studente {
    string nome;
    int eta;
    float media;
};

int main() {
    // Oggetto singolo dinamico
    Studente* ps = new Studente{"Luca", 18, 7.9f};
    cout << ps->nome << " - " << ps->media << endl;
    ps->media = 8.4f;
    delete ps; ps = nullptr;

    // Array dinamico di struct
    int n = 3;
    Studente* classe = new Studente[n]{
        {"Anna", 17, 8.1f},
        {"Marco", 18, 9.0f},
        {"Giulia", 17, 7.2f}
    };
    for (int i = 0; i < n; i++)
        cout << (classe+i)->nome << " - " << (classe+i)->media << endl;

    delete[] classe; classe = nullptr;
}

5️⃣ Errori comuni (e come evitarli)

ErroreEsempioSoluzione
Memory leak (perdita di memoria) int* p = new int(5); /* niente delete */ Sempre delete/delete[] quando finito. Azzera il puntatore a nullptr.
delete sbagliato int* v = new int[10]; delete v; // ❌ Per gli array usa delete[] v;
Double delete Chiamare delete due volte sullo stesso puntatore Dopo delete metti p = nullptr; e verifica if (p) prima di cancellare.
Dangling pointer Usare *p dopo il delete Subito dopo il delete, imposta p = nullptr;
Non inizializzare int* p; *p = 3; // ❌ Inizializza sempre: int* p = new int(3); oppure p = nullptr;

6️⃣ Passare memoria dinamica alle funzioni

Le funzioni ricevono puntatori (all array o all oggetto) e possono modificarne il contenuto.

void raddoppia(int* v, int n) {
    for (int i = 0; i < n; i++) v[i] *= 2;
}

Studente* migliore(Studente* v, int n) {
    Studente* best = v;
    for (int i = 1; i < n; i++)
        if ((v+i)->media > best->media) best = (v+i);
    return best; // attenzione: v deve rimanere valido nel chiamante
}

7️⃣ Nota moderna: smart pointer (anteprima)

Nel C++ moderno, per evitare errori di gestione manuale, si usano spesso gli smart pointer (es. std::unique_ptr). Per ora lavoriamo con new/delete per capire cosa succede. In seguito potrai sostituirli con:

#include <memory>
auto p = std::make_unique<int>(42);     // niente delete manuale
auto a = std::make_unique<int[]>(n);    // array dinamico
LABORATORIO

Gestione Dinamica dei Posti in un Parcheggio

In questo laboratorio simulerai il funzionamento di un parcheggio intelligente che utilizza la memoria dinamica per gestire i posti disponibili. L obiettivo è imparare ad allocare e liberare memoria nello heap usando new e delete[], evitando errori di gestione.


1 Allocazione dinamica dei posti

L utente inserisce quanti posti ha il parcheggio. Il programma crea dinamicamente un array di interi per rappresentarli.

int n;
cout << "Quanti posti ha il parcheggio? ";
cin >> n;

int* posti = new int[n]; // allocazione dinamica

for (int i = 0; i < n; i++) {
    posti[i] = 0; // inizialmente tutti liberi
}

2 Visualizzazione e modifica

Mostra i posti e consenti di occuparne o liberarli tramite l indice.

int scelta, indice;
do {
    cout << "\n1. Visualizza posti\n2. Occupa posto\n3. Libera posto\n0. Esci\nScelta: ";
    cin >> scelta;

    if (scelta == 1) {
        cout << "Stato posti: ";
        for (int i = 0; i < n; i++) cout << posti[i] << " ";
        cout << endl;
    }
    else if (scelta == 2) {
        cout << "Numero posto da occupare: ";
        cin >> indice;
        if (indice >= 0 && indice < n) posti[indice] = 1;
    }
    else if (scelta == 3) {
        cout << "Numero posto da liberare: ";
        cin >> indice;
        if (indice >= 0 && indice < n) posti[indice] = 0;
    }
} while (scelta != 0);

3 Deallocazione della memoria

Al termine del programma libera la memoria occupata dalla struttura dinamica.

delete[] posti;
posti = nullptr;
cout << "Memoria liberata correttamente." << endl;

4 Approfondimenti

  • Estendi il programma aggiungendo una funzione per contare i posti liberi e occupati.
  • Gestisci un errore di input (ad esempio inserimento di un indice non valido).
  • Prova a dimenticare delete[] e osserva cosa succede (memory leak).