Lo stack overflow è una situazione in cui un programma consuma più memoria dello stack di quanto assegnato dal sistema. Storicamente, lo stack è stato utilizzato per memorizzare variabili locali, indirizzi di ritorno e dati temporanei durante le chiamate di funzione. Nelle prime implementazioni di C, lo stack era piuttosto piccolo e non protetto da accessi oltre i limiti.
Il problema si verifica quando una funzione o una catena di chiamate di funzione utilizza troppe variabili locali o chiama funzioni ricorsive senza una condizione di uscita, portando il programma a scrivere dati oltre la memoria riservata allo stack, causando errori, crash e vulnerabilità.
Soluzione — progettare funzioni che siano modeste in termini di memoria, evitare ricorsioni profonde o infinite e non allocare grandi oggetti nello stack. Il sistema operativo può prevenire l'overflow tramite la protezione delle pagine di memoria (guard pages), tuttavia il programmatore è responsabile della scrittura di codice che non porti a un overflow.
Esempio di codice che provoca uno stack overflow a causa della ricorsione infinita:
void foo() { int arr[1000]; // Grande array locale che aggrava il problema foo(); // Chiamata ricorsiva senza uscita } int main() { foo(); return 0; }
Caratteristiche chiave:
Cosa succede se dichiari un array locale molto grande all'interno di una funzione (ad esempio, int arr[1000000])?
Risposta: Un grande array locale può immediatamente utilizzare tutto lo stack. A seconda del sistema operativo e del compilatore, ciò porterà a un crash all'avvio della funzione o addirittura a un crash del programma.
Esempio di codice:
void func() { int arr[1000000]; // Molta memoria arr[0] = 1; }
La ricorsione porta sempre a un overflow dello stack?
Risposta: No, la ricorsione è utile se limitata in profondità. L'overflow si verifica solo se la profondità della ricorsione è alta o non è limitata.
È possibile allocare grandi array statici all'interno delle funzioni per risparmiare memoria?
Risposta: No, grandi array statici all'interno di una funzione occupano comunque memoria, ma nel segmento dei dati statici, non nello stack. Questo non è sempre economico, specialmente se è necessaria memoria locale temporanea.
Esempio di codice:
void func() { static int arr[1000000]; // Non nello stack, ma l'area statica è occupata per sempre }
Un programmatore ha implementato un ordinamento rapido tramite ricorsione per ordinare un grande array, senza limitare la profondità delle chiamate e senza utilizzare condizioni di uscita. Il codice ha portato a uno stack overflow durante l'elaborazione di dati reali.
Pro:
Contro:
Un altro programmatore ha utilizzato un'implementazione iterativa con un proprio piccolo stack nell'heap, controllando la profondità della ricorsione e allocando grandi array temporanei tramite malloc.
Pro:
Contro: