En la práctica, es difícil (y a veces imposible) hacer crecer la pila. Para entender por qué requiere cierta comprensión de la memoria virtual.
En los días de Ye Olde de aplicaciones de un solo subproceso y memoria contigua, tres eran tres componentes de un espacio de direcciones de proceso: el código, el montón y la pila. La forma en que se distribuyeron esos tres dependía del sistema operativo, pero generalmente el código vino primero, comenzando en la parte inferior de la memoria, el montón vino después y creció hacia arriba, y la pila comenzó en la parte superior de la memoria y creció hacia abajo. También había algo de memoria reservada para el sistema operativo, pero podemos ignorar eso. Los programas en esos días tenían desbordamientos de pila algo más dramáticos: la pila se bloqueaba en el montón, y dependiendo de qué se actualizara primero, trabajaría con datos incorrectos o regresaría de una subrutina a alguna parte arbitraria de la memoria.
La gestión de la memoria cambió este modelo de alguna manera: desde la perspectiva del programa, todavía tenía los tres componentes de un mapa de memoria de proceso, y generalmente estaban organizados de la misma manera, pero ahora cada uno de los componentes se gestionaba como un segmento independiente y la MMU señalaba el SO si el programa intentó acceder a la memoria fuera de un segmento. Una vez que tenía memoria virtual, no había necesidad ni deseo de dar acceso a un programa a todo su espacio de direcciones. Entonces a los segmentos se les asignaron límites fijos.
Entonces, ¿por qué no es conveniente dar acceso a un programa a su espacio de direcciones completo? Porque esa memoria constituye un "cargo de compromiso" contra el intercambio; en cualquier momento, una parte o la totalidad de la memoria de un programa podría tener que escribirse para intercambiarse y dejar espacio para la memoria de otro programa. Si cada programa pudiera consumir potencialmente 2 GB de intercambio, entonces tendría que proporcionar suficiente intercambio para todos sus programas o arriesgarse a que dos programas necesiten más de lo que podrían obtener.
En este punto, suponiendo que haya suficiente espacio de direcciones virtuales, puede ampliar estos segmentos si es necesario, y el segmento de datos (montón) de hecho crece con el tiempo: comienza con un pequeño segmento de datos y cuando el asignador de memoria solicita más espacio cuando es necesario En este punto, con una sola pila, habría sido físicamente posible extender el segmento de la pila: el sistema operativo podría atrapar el intento de empujar algo fuera del segmento y agregar más memoria. Pero esto tampoco es particularmente deseable.
Ingrese multi-threading. En este caso, cada subproceso tiene un segmento de pila independiente, de nuevo tamaño fijo. Pero ahora los segmentos se disponen uno tras otro en el espacio de direcciones virtuales, por lo que no hay forma de expandir un segmento sin mover otro, lo que no puede hacer porque el programa potencialmente tendrá punteros a la memoria que viven en la pila. Alternativamente, podría dejar algo de espacio entre segmentos, pero ese espacio se desperdiciaría en casi todos los casos. Un mejor enfoque era poner la carga sobre el desarrollador de la aplicación: si realmente necesita pilas profundas, puede especificar eso al crear el hilo.
Hoy, con un espacio de direcciones virtuales de 64 bits, podríamos crear pilas efectivamente infinitas para números de hilos efectivamente infinitos. Pero, de nuevo, eso no es particularmente deseable: en casi todos los casos, una pila demasiado baja indica un error con su código. Proporcionarle una pila de 1 GB simplemente difiere el descubrimiento de ese error.