Cos'è un Puntatore?

Un puntatore è una variabile che contiene l'indirizzo di memoria di un'altra variabile. Ogni cella di memoria ha un indirizzo unico (tipicamente espresso in esadecimale).

COperatori & (indirizzo) e * (dereferenziazione)
#include <stdio.h>

int main() {
    int x = 42;
    int *p;       // Dichiara un puntatore a int

    p = &x;       // p contiene l'indirizzo di x (operatore &)

    printf("Valore di x:         %d\n",  x);
    printf("Indirizzo di x (&x): %p\n",  (void*)&x);
    printf("p contiene:          %p\n",  (void*)p);
    printf("*p (dereferenziato): %d\n",  *p);  // Legge x tramite p

    // Modificare x tramite il puntatore
    *p = 99;
    printf("\nDopo *p = 99:\n");
    printf("x = %d (cambiato tramite puntatore)\n", x);

    // Puntatore a puntatore
    int **pp = &p;
    printf("\n**pp = %d\n", **pp);  // Doppia dereferenziazione

    return 0;
}
Output
Valore di x:         42
Indirizzo di x (&x): 0x7ffd1a2b3c40
p contiene:          0x7ffd1a2b3c40
*p (dereferenziato): 42

Dopo *p = 99:
x = 99 (cambiato tramite puntatore)

**pp = 99

Puntatori e Array

Il nome di un array è un puntatore al primo elemento. L'aritmetica dei puntatori tiene conto della dimensione del tipo puntato.

CAritmetica dei puntatori
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;    // p punta al primo elemento (arr[0])

    // Le due notazioni sono equivalenti
    printf("arr[2]    = %d\n", arr[2]);   // Notazione array
    printf("*(p + 2)  = %d\n", *(p + 2)); // Notazione puntatore

    // Iterare con puntatore
    printf("\nIterazione con puntatore:\n");
    for (int *ptr = arr; ptr < arr + 5; ptr++) {
        printf("%d ", *ptr);
    }
    printf("\n");

    // Differenza tra puntatori (quanti elementi separano i due)
    int *inizio = arr;
    int *fine   = arr + 4;
    printf("Distanza: %ld elementi\n", fine - inizio);  // 4

    // p++ sposta di sizeof(int) byte (4 byte su x64)
    printf("\np ora punta a arr[0]: %d\n", *p);
    p++;
    printf("Dopo p++, punta a arr[1]: %d\n", *p);

    return 0;
}

Allocazione Dinamica della Memoria

Con malloc, calloc e realloc puoi allocare memoria a runtime (nello heap). Devi sempre liberarla con free.

Cmalloc, calloc, realloc, free
#include <stdio.h>
#include <stdlib.h>   // malloc, calloc, realloc, free
#include <string.h>

int main() {
    int n;
    printf("Quanti interi da allocare? ");
    scanf("%d", &n);

    // malloc: alloca n*sizeof(int) byte NON inizializzati
    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Errore: allocazione fallita!\n");
        return 1;
    }

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

    printf("Array allocato dinamicamente: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    // calloc: alloca e AZZERA la memoria
    int *azzerato = calloc(n, sizeof(int));
    printf("calloc (tutti 0): ");
    for (int i = 0; i < n; i++) printf("%d ", azzerato[i]);
    printf("\n");

    // realloc: ridimensiona
    int nuova_dim = n * 2;
    arr = realloc(arr, nuova_dim * sizeof(int));
    for (int i = n; i < nuova_dim; i++) arr[i] = (i + 1) * 10;

    printf("Dopo realloc (x2): ");
    for (int i = 0; i < nuova_dim; i++) printf("%d ", arr[i]);
    printf("\n");

    // SEMPRE liberare la memoria!
    free(arr);
    free(azzerato);
    arr = NULL;     // Buona pratica: evita dangling pointer
    azzerato = NULL;

    return 0;
}
Memory Leak e Dangling Pointer Due errori classici: 1) Memory leak: dimentichi di chiamare free() → la memoria non viene mai rilasciata. 2) Dangling pointer: usi un puntatore dopo free() → comportamento indefinito. Usa sempre ptr = NULL dopo free(ptr).

Lista Collegata — Applicazione Pratica

La lista collegata è una struttura dati fondamentale che sfrutta puntatori e allocazione dinamica.

CLinked List semplice
#include <stdio.h>
#include <stdlib.h>

typedef struct Nodo {
    int valore;
    struct Nodo *prossimo;
} Nodo;

// Inserisce in testa
Nodo* inserisci(Nodo *testa, int val) {
    Nodo *nuovo = malloc(sizeof(Nodo));
    nuovo->valore   = val;
    nuovo->prossimo = testa;
    return nuovo;
}

// Stampa la lista
void stampa(Nodo *testa) {
    for (Nodo *p = testa; p != NULL; p = p->prossimo)
        printf("%d -> ", p->valore);
    printf("NULL\n");
}

// Libera tutta la memoria
void libera(Nodo *testa) {
    while (testa != NULL) {
        Nodo *tmp = testa->prossimo;
        free(testa);
        testa = tmp;
    }
}

int main() {
    Nodo *lista = NULL;

    for (int i = 1; i <= 5; i++)
        lista = inserisci(lista, i * 10);

    printf("Lista: ");
    stampa(lista);

    libera(lista);
    lista = NULL;
    return 0;
}
🏋️ Esercizi
I puntatori sono fondamentali: pratica molto!
1
Funzioni con puntatori
FacilePuntatori base

Scrivi una funzione min_max(arr, n, min, max) che riceva un array e due puntatori a int. La funzione deve riempire *min e *max con il minimo e massimo dell'array. Usala nel main.

C
#include <stdio.h>

void min_max(int arr[], int n, int *min, int *max) {
    *min = *max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

int main() {
    int arr[] = {34, 7, 23, 89, 12, 56, 1, 99, 45};
    int n = sizeof(arr) / sizeof(arr[0]);
    int minimo, massimo;

    min_max(arr, n, &minimo, &massimo);
    printf("Min: %d, Max: %d\n", minimo, massimo);
    return 0;
}
2
Array dinamico
Mediomalloc / realloc

Implementa un array dinamico che parta con capacità 4, e si raddoppi ogni volta che si riempie. Scrivi le funzioni: crea(), aggiungi(arr, valore), stampa(arr), distruggi(arr). Usa una struct che tenga traccia di dimensione e capacità.

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

typedef struct {
    int *dati;
    int dimensione;
    int capacita;
} ArrayDinamico;

ArrayDinamico crea() {
    ArrayDinamico a;
    a.capacita   = 4;
    a.dimensione = 0;
    a.dati = malloc(a.capacita * sizeof(int));
    return a;
}

void aggiungi(ArrayDinamico *a, int val) {
    if (a->dimensione == a->capacita) {
        a->capacita *= 2;
        a->dati = realloc(a->dati, a->capacita * sizeof(int));
        printf("[Realloc → capacità: %d]\n", a->capacita);
    }
    a->dati[a->dimensione++] = val;
}

void stampa(ArrayDinamico *a) {
    printf("Size=%d Cap=%d: [", a->dimensione, a->capacita);
    for (int i = 0; i < a->dimensione; i++)
        printf("%d%s", a->dati[i], idimensione-1 ? ", " : "");
    printf("]\n");
}

void distruggi(ArrayDinamico *a) {
    free(a->dati);
    a->dati = NULL;
    a->dimensione = a->capacita = 0;
}

int main() {
    ArrayDinamico arr = crea();
    for (int i = 1; i <= 10; i++) {
        aggiungi(&arr, i * 5);
        stampa(&arr);
    }
    distruggi(&arr);
    return 0;
}
3
Copia profonda di stringhe
Mediomalloc / string

Scrivi una funzione clona_stringa(s) che allochi dinamicamente una copia di una stringa e la restituisca. Poi scrivi inverti_stringa(s) che inverta una stringa in-place usando puntatori (senza array aggiuntivi).

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

char* clona_stringa(const char *s) {
    char *copia = malloc(strlen(s) + 1);
    if (copia) strcpy(copia, s);
    return copia;
}

void inverti_stringa(char *s) {
    char *inizio = s;
    char *fine   = s + strlen(s) - 1;
    while (inizio < fine) {
        char tmp  = *inizio;
        *inizio++ = *fine;
        *fine--   = tmp;
    }
}

int main() {
    const char *orig = "Hello, World!";
    char *clone = clona_stringa(orig);

    printf("Originale: %s\n", orig);
    printf("Clone:     %s\n", clone);

    inverti_stringa(clone);
    printf("Invertita: %s\n", clone);

    free(clone);
    return 0;
}
4
Matrice dinamica 2D
DifficilePuntatori a puntatori

Alloca dinamicamente una matrice MxN usando int ** (array di puntatori a array). Riempila con i valori della tavola pitagorica. Poi libera tutta la memoria correttamente (prima le righe, poi l'array di puntatori).

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

int main() {
    int M, N;
    printf("Righe e colonne: ");
    scanf("%d %d", &M, &N);

    // Alloca array di M puntatori
    int **mat = malloc(M * sizeof(int *));
    for (int i = 0; i < M; i++) {
        mat[i] = malloc(N * sizeof(int));
        for (int j = 0; j < N; j++)
            mat[i][j] = (i+1) * (j+1);
    }

    // Stampa
    printf("\nTavola pitagorica %dx%d:\n", M, N);
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++)
            printf("%4d", mat[i][j]);
        printf("\n");
    }

    // Libera
    for (int i = 0; i < M; i++) free(mat[i]);
    free(mat);
    return 0;
}
5
Doppia lista collegata
DifficileLista / puntatori

Implementa una doubly linked list con operazioni: inserisci_in_testa, inserisci_in_coda, elimina_nodo(valore), stampa_avanti, stampa_indietro. Ogni nodo ha puntatori a prev e next.

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

typedef struct Nodo {
    int val;
    struct Nodo *prev, *next;
} Nodo;

typedef struct { Nodo *testa, *coda; } Lista;

Nodo* nuovo_nodo(int v) {
    Nodo *n = malloc(sizeof(Nodo));
    n->val = v; n->prev = n->next = NULL;
    return n;
}

void in_testa(Lista *l, int v) {
    Nodo *n = nuovo_nodo(v);
    if (!l->testa) { l->testa = l->coda = n; return; }
    n->next = l->testa;
    l->testa->prev = n;
    l->testa = n;
}

void in_coda(Lista *l, int v) {
    Nodo *n = nuovo_nodo(v);
    if (!l->coda) { l->testa = l->coda = n; return; }
    l->coda->next = n;
    n->prev = l->coda;
    l->coda = n;
}

void elimina(Lista *l, int v) {
    for (Nodo *n = l->testa; n; n = n->next) {
        if (n->val == v) {
            if (n->prev) n->prev->next = n->next; else l->testa = n->next;
            if (n->next) n->next->prev = n->prev; else l->coda = n->prev;
            free(n); return;
        }
    }
}

void stampa_avanti(Lista *l) {
    for (Nodo *n = l->testa; n; n = n->next) printf("%d ", n->val);
    printf("\n");
}

void stampa_indietro(Lista *l) {
    for (Nodo *n = l->coda; n; n = n->prev) printf("%d ", n->val);
    printf("\n");
}

int main() {
    Lista l = {NULL, NULL};
    for (int i = 1; i <= 5; i++) in_coda(&l, i*10);
    in_testa(&l, 5);
    printf("Avanti:   "); stampa_avanti(&l);
    printf("Indietro: "); stampa_indietro(&l);
    elimina(&l, 30);
    printf("Dopo elimina(30): "); stampa_avanti(&l);
    return 0;
}