📌 malloc / free in C

Allocazione dinamica: stringhe (char*) e vettori di interi (int*) — malloc e free

🧠 Perché malloc e free?

In C, malloc e free permettono di gestire la memoria nell'heap a runtime. malloc alloca un blocco di byte, restituendo un puntatore void*; free rilascia il blocco. Ogni malloc deve avere il suo free per evitare memory leak.

📌 Regola d'oro: per ogni allocazione con malloc o calloc, esattamente una chiamata a free quando la memoria non serve più.

🔤 Stringa dinamica (char *)

Per creare una stringa modificabile a runtime, allochiamo spazio con malloc per lunghezza + 1 (terminatore '\0').

✍️ Esempio completo

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 1. Allochiamo spazio per 30 caratteri (29 + terminatore)
    char *nome = (char*)malloc(30 * sizeof(char));
    
    // 2. Controllo di errore fondamentale!
    if (nome == NULL) {
        printf("Errore allocazione memoria\n");
        return 1;
    }
    
    // 3. Copia della stringa
    strcpy(nome, "Alice");
    printf("Nome: %s\n", nome);
    
    // 4. Per estendere la stringa:  
    //    Allochiamo NUOVA area più grande, copiamo e liberiamo la vecchia.
    char *nuovo_nome = (char*)malloc(60 * sizeof(char));
    if (nuovo_nome != NULL) {
        strcpy(nuovo_nome, nome);
        strcat(nuovo_nome, " - studentessa dinamica");
        free(nome);          // libero il vecchio blocco
        nome = nuovo_nome;     // aggiorno il puntatore
        printf("Nome esteso: %s\n", nome);
    } else {
        printf("Estensione fallita, manteniamo il nome originale.\n");
    }
    
    // 5. Liberazione finale
    free(nome);
    nome = NULL;
    
    return 0;
}

📖 Spiegazione strategia "malloc + free"

  • malloc iniziale: 30 * sizeof(char) = 30 byte.
  • ✅ Per espandere la stringa → nuova allocazione (più grande), copia con strcpy/strcat, poi free del vecchio blocco.
  • ✅ Alla fine free obbligatorio + puntatore a NULL.
💡 Vantaggio didattico: allocare una nuova area e liberare la vecchia è un pattern pulito per comprendere il ciclo di vita della memoria.

📊 Vettore dinamico di interi (int *)

Array di int con dimensione scelta a runtime. Usiamo malloc e per estendere la capacità: nuovo array più grande, copia manuale e infine free.

✍️ Esempio: array modificabile

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("Quanti elementi vuoi inserire? ");
    scanf("%d", &n);
    
    // Allocazione vettore di n interi
    int *vett = (int*)malloc(n * sizeof(int));
    if (vett == NULL) {
        printf("Allocazione fallita\n");
        return 1;
    }
    
    // Riempimento
    for (int i = 0; i < n; i++) {
        vett[i] = (i + 1) * 10;
    }
    
    printf("Vettore iniziale: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", vett[i]);
    }
    printf("\n");
    
    // --- Espansione del vettore: nuova capacità (n*2) ---
    int nuova_capacita = n * 2;
    int *nuovo_vett = (int*)malloc(nuova_capacita * sizeof(int));
    
    if (nuovo_vett != NULL) {
        // Copia elementi vecchi
        for (int i = 0; i < n; i++) {
            nuovo_vett[i] = vett[i];
        }
        // Riempiamo i nuovi elementi
        for (int i = n; i < nuova_capacita; i++) {
            nuovo_vett[i] = 100 + i;
        }
        
        free(vett);        // rilascio vecchio array
        vett = nuovo_vett;   // riaggancio al nuovo blocco
        
        printf("Vettore espanso (dimensione %d): ", nuova_capacita);
        for (int i = 0; i < nuova_capacita; i++) {
            printf("%d ", vett[i]);
        }
        printf("\n");
    } else {
        printf("Espansione fallita, mantengo vettore originale.\n");
    }
    
    // Liberazione finale
    free(vett);
    vett = NULL;
    
    return 0;
}

📌 Pattern spiegato

  • malloc iniziale: n * sizeof(int) byte.
  • ✅ Per espandere: malloc nuova area → copia manuale (ciclo for) → free vecchia area → aggiornamento puntatore.
  • ✅ Alla fine, free + NULL per sicurezza.

🧹 free() e gestione sicura della memoria

✅ Come funziona free

int *data = (int*)malloc(100 * sizeof(int));
// ... operazioni ...
free(data);     
data = NULL;   // Evita dangling pointer

free non modifica il puntatore, ma rende la memoria nuovamente disponibile. Accedere dopo free è undefined behavior.

⚠️ Errori frequenti

  • Memory leak: dimenticare free → memoria occupata fino alla fine del programma.
  • Double free: chiamare free due volte sullo stesso puntatore → crash.
  • Use after free: dereferenziare memoria già liberata.
  • Free di puntatori stack o NULL (free(NULL) è safe, ma non su stack).
Esempio di double free: free(ptr); free(ptr); → corruzione heap. Imposta sempre ptr = NULL dopo free.

⚖️ Tabella riassuntiva: stringa dinamica vs vettore interi

Caratteristica Stringa dinamica (char*) Vettore dinamico (int*)
Allocazione tipica malloc((len + 1) * sizeof(char)) malloc(n * sizeof(int))
Terminatore obbligatorio Sì, '\0' per le funzioni stringa No, si conosce la dimensione separatamente
Espansione malloc nuova area → strcpy → free vecchia malloc nuova area → ciclo copia → free vecchia
Liberazione free(ptr); ptr = NULL;

📖 Regole essenziali per malloc / free

  • 1. Includere #include <stdlib.h>
  • 2. Usare sizeof per la portabilità
  • 3. Controllare sempre il valore di ritorno (NULL → errore)
  • 4. Ogni malloc → un solo free
  • 5. Dopo free impostare il puntatore a NULL
  • 6. Mai accedere a memoria liberata
  • 7. Per estendere: nuova allocazione + copia + free vecchia
  • 8. Usa Valgrind per trovare leak