In pratica, è difficile (e talvolta impossibile) far crescere lo stack. Per capire perché richiede una certa comprensione della memoria virtuale.
In Ye Olde Days, con applicazioni a thread singolo e memoria contigua, tre erano tre componenti di uno spazio degli indirizzi di processo: il codice, l'heap e lo stack. Il modo in cui quei tre erano disposti dipendeva dal sistema operativo, ma in genere il codice veniva prima, iniziando dal fondo della memoria, l'heap veniva poi e cresceva verso l'alto, e lo stack iniziava in cima alla memoria e cresceva verso il basso. C'era anche un po 'di memoria riservata al sistema operativo, ma possiamo ignorarlo. I programmi in quei giorni avevano degli overflow dello stack in qualche modo più drammatici: lo stack si schiantava nell'heap e, a seconda di quale aggiornamento veniva prima, si lavorava con dati errati o si tornava da una subroutine a una parte arbitraria della memoria.
La gestione della memoria ha in qualche modo cambiato questo modello: dal punto di vista del programma c'erano ancora i tre componenti di una mappa di memoria di processo, ed erano generalmente organizzati allo stesso modo, ma ora ciascuno dei componenti era gestito come un segmento indipendente e la MMU segnalava Sistema operativo se il programma ha tentato di accedere alla memoria all'esterno di un segmento. Una volta che avevi memoria virtuale, non c'era bisogno o desiderio di dare a un programma l'accesso a tutto il suo spazio di indirizzi. Quindi ai segmenti sono stati assegnati limiti fissi.
Quindi perché non è desiderabile dare a un programma l'accesso al suo spazio di indirizzi completo? Perché quella memoria costituisce una "commissione di commit" contro lo swap; in qualsiasi momento potrebbe essere necessario scrivere una parte o tutta la memoria per un programma per fare spazio alla memoria di un altro programma. Se ogni programma potrebbe potenzialmente consumare 2 GB di scambio, allora dovresti fornire uno scambio sufficiente per tutti i tuoi programmi o avere la possibilità che due programmi avrebbero bisogno di più di quanto potrebbero ottenere.
A questo punto, assumendo uno spazio di indirizzi virtuale sufficiente, è possibile estendere questi segmenti, se necessario, e il segmento di dati (heap) infatti aumenta nel tempo: si inizia con un piccolo segmento di dati e quando l'allocatore di memoria richiede più spazio quando è necessario. A questo punto, con un singolo stack, sarebbe stato fisicamente possibile estendere il segmento dello stack: il sistema operativo potrebbe intrappolare il tentativo di spingere qualcosa al di fuori del segmento e aggiungere più memoria. Ma neanche questo è particolarmente desiderabile.
Inserisci il multi-threading. In questo caso, ogni thread ha un segmento di stack indipendente, di nuovo a dimensione fissa. Ma ora i segmenti sono disposti uno dopo l'altro nello spazio degli indirizzi virtuali, quindi non c'è modo di espandere un segmento senza spostarne un altro, cosa che non si può fare perché il programma avrà potenzialmente puntatori alla memoria che vivono nello stack. In alternativa, potresti lasciare un po 'di spazio tra i segmenti, ma quello spazio sarebbe sprecato in quasi tutti i casi. Un approccio migliore è stato quello di gravare sullo sviluppatore dell'applicazione: se avessi davvero bisogno di stack profondi, potresti specificarlo durante la creazione del thread.
Oggi, con uno spazio di indirizzi virtuali a 64 bit, potremmo creare stack effettivamente infiniti per un numero effettivamente infinito di thread. Ma ancora una volta, questo non è particolarmente desiderabile: in quasi tutti i casi, uno stack overlow indica un bug con il tuo codice. Fornire uno stack da 1 GB difende semplicemente la scoperta di quel bug.