Puntatori
Il concetto più potente e pericoloso del C: indirizzi di memoria, aritmetica e allocazione dinamica.
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).
#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;
}
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.
#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.
#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;
}
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.
#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;
}
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.
#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;
}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à.
#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;
} 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).
#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;
}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).
#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;
}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.
#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;
}