In pratica, è difficile (e talvolta impossibile) far crescere lo stack. Per capire perché richiede una certa comprensione della memoria virtuale.
In Ye Olde Days di applicazioni a thread singolo e memoria contigua, tre erano tre componenti di uno spazio di indirizzamento del processo: il codice, l'heap e lo stack. Il modo in cui i tre erano disposti dipendeva dal sistema operativo, ma in genere il codice veniva prima, iniziando dal fondo della memoria, il mucchio si avvicinava e cresceva, e lo stack iniziava nella parte superiore della 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 overflow dello stack un po 'più drammatici: lo stack si sarebbe schiantato nell'heap e, a seconda di quale aggiornamento si sarebbe aggiornato, avresti lavorato con dati errati o tornato da una subroutine in qualche parte arbitraria della memoria.
La gestione della memoria ha cambiato questo modello in qualche modo: dal punto di vista del programma c'erano ancora i tre componenti di una mappa della memoria di processo, e generalmente erano organizzati nello stesso modo, ma ora ciascuno dei componenti era gestito come un segmento indipendente e la MMU segnalerebbe il sistema operativo se il programma ha tentato di accedere alla memoria al di fuori di un segmento. Una volta che avevi memoria virtuale, non c'era bisogno di o desiderio di dare a un programma l'accesso all'intero spazio degli indirizzi. Quindi ai segmenti sono stati assegnati dei limiti fissi.
So why isn't it desirable to give a program access to its full address space? Because that memory constitutes a "commit charge" against the swap; at any time any or all of the memory for one program might have to be written to swap to make room for another program's memory. If every program could potentially consume 2GB of swap, then either you'd have to provide enough swap for all of your programs or take the chance that two programs would need more than they could get.
A questo punto, presupponendo uno spazio di indirizzamento virtuale sufficiente, potrebbe estendere questi segmenti se necessario, e il segmento di dati (heap) cresce effettivamente nel tempo: si inizia con un segmento di dati di piccole dimensioni e quando l'allocatore di memoria richiede più spazio quando è necessario. A questo punto, con uno stack singolo, 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 auspicabile.
Inserisci multi-threading. In questo caso, ogni thread ha un segmento di stack indipendente, di nuovo una 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 dei puntatori alla memoria che vive nello stack. In alternativa potresti lasciare uno spazio tra i segmenti, ma quello spazio sarebbe sprecato in quasi tutti i casi. Un approccio migliore era quello di mettere il peso sullo sviluppatore dell'applicazione: se davvero avevi bisogno di stack profondi, puoi specificarlo durante la creazione del thread.
Oggi, con uno spazio di indirizzi virtuali a 64 bit, potremmo creare stack infinitamente efficaci per un numero infinito di thread. Ma ancora una volta, ciò non è particolarmente auspicabile: in quasi tutti i casi, uno stack overflow indica un bug con il tuo codice. Fornendoti uno stack da 1 GB, puoi semplicemente rinviare la scoperta di quel bug.