El desbordamiento de pila (stack overflow) es una situación en la que un programa utiliza más memoria de pila de la que ha sido asignada por el sistema. Históricamente, la pila se ha utilizado para almacenar variables locales, direcciones de retorno y datos temporales durante las llamadas a funciones. En implementaciones tempranas de C, la pila era bastante pequeña y no estaba protegida contra desbordamientos.
Problema ocurre cuando una función o una cadena de llamadas a funciones utiliza demasiadas variables locales o llama a funciones recursivas sin una condición de salida, lo que provoca que el programa escriba datos fuera de la memoria asignada de la pila, resultando en errores, fallos y vulnerabilidades.
Solución: diseñar funciones que consuman poca memoria, evitar recursiones profundas o infinitas, no colocar objetos grandes en la pila. El sistema operativo puede prevenir desbordamientos mediante la protección de segmentos de memoria (guard pages), sin embargo, el desarrollador debe escribir código que no conduzca a un desbordamiento.
Ejemplo de código que provoca un desbordamiento de pila debido a recursión infinita:
void foo() { int arr[1000]; // Un gran arreglo local solo acelera el problema foo(); // Llamada recursiva sin salida } int main() { foo(); return 0; }
Características clave:
¿Qué ocurrirá si se declara un arreglo local muy grande dentro de una función (por ejemplo, int arr[1000000])?
Respuesta: Un gran arreglo local puede utilizar inmediatamente toda la pila. Dependiendo del SO y del compilador, esto resultará en un fallo al iniciar la función o incluso en un colapso del programa.
Ejemplo de código:
void func() { int arr[1000000]; // Mucha memoria arr[0] = 1; }
¿La recursión siempre conduce a un desbordamiento de pila?
Respuesta: No, la recursión es útil si está limitada en profundidad. El desbordamiento ocurre solo si la profundidad de la recursión es grande o no está limitada.
¿Se pueden colocar grandes arreglos estáticos dentro de funciones para ahorrar memoria?
Respuesta: No, los grandes arreglos estáticos dentro de la función aún ocupan memoria, pero en la sección de datos estáticos, no en la pila. Esto no siempre es económico, especialmente si se necesita memoria local temporal.
Ejemplo de código:
void func() { static int arr[1000000]; // No en la pila, pero la área estática ocupada para siempre }
Un programador implementó un algoritmo de ordenación rápida a través de recursión para ordenar un gran arreglo, sin limitar la profundidad de llamadas y sin usar condiciones de salida. El código provocaba un desbordamiento de pila al procesar datos reales.
Pros:
Contras:
Otro programador utilizó una implementación iterativa con su propia pequeña pila en el montón, controló la profundidad de recursión y asignó grandes arreglos temporales mediante malloc.
Pros:
Contras: