2009-10-16 10 views
9

¿Es posible capturar stack overflow exception en una función recursiva de C++? ¿Si es así, cómo?Captura de excepciones de "Desbordamiento de pila" en funciones recursivas de C++

así que lo que va a pasar en este caso

void doWork() 
{ 

    try() { 

    doWork();  
    } 


    catch(...) { 

    doWork();  
    } 
} 

No estoy buscando una respuesta a determinado sistema operativo. Solo en general

+0

enviar el código? Use Lint para el análisis estático –

+0

He agregado el "¿cómo?" pregunta de seguimiento porque de lo contrario, esta es solo una pregunta sí/no que no es muy interesante o útil. La porción * how * es lo que hace que esta sea una pregunta útil. –

+0

No transportable, no. ¿Estás buscando una respuesta para un sistema operativo específico? –

Respuesta

0

Esto lo hace todo el tiempo la mayoría de los sistemas operativos modernos. Si quieres hacerlo por tu cuenta, deberás conocer la dirección máxima "segura" para tu pila (o también hacer algunos cálculos para determinar cuántas veces puedes llamar a la función de forma segura), pero esto puede ser muy complicado. si no está administrando la pila de llamadas usted mismo, ya que el sistema operativo generalmente (por una buena razón) le ocultará esto.

Si está programando en el espacio del kernel, esto se vuelve mucho más fácil, pero aún hay algo que cuestiono por qué lo está haciendo. Si tiene un desbordamiento de pila, es probable que sea por una mala decisión algorítmica o un error en el código.

editar: acaba de darse cuenta de que desea "atrapar la excepción" que resulta. No creo que mi respuesta responda directamente a eso (¿existe siquiera esta excepción? En lugar de eso, encontraría un fracaso espectacular), pero lo dejo para una idea. Si quiere que se elimine, háganmelo saber en los comentarios y lo haré.

+0

Supongo que debo mencionarlo: supuse que la pila de tu sistema "bajaba". La misma lógica se aplica si es al revés, sin embargo. Simplemente invierta el operador de comparación :) –

1

Dudo que, cuando la pila se desborde, el programa no podrá ni siquiera manejar la excepción. Normalmente, el sistema operativo cerrará dicho programa e informará el error.
Esto sucede principalmente debido a infinitas recursiones.

+1

"Manejar" no es lo mismo que "atrapar". Si el entorno ofrece una excepción, deberías poder atraparlo. Sin embargo, eso no significa que puedas * recuperar * de ella. Atrápalo, conéctate y sal. –

+0

Derecha, gracias. –

4

¿En qué sistema operativo? Solo por ejemplo, puede hacerlo en Windows utilizando el Manejo de excepciones estructuradas (o Manejo de excepciones vectorizadas). Sin embargo, normalmente no puedes hacerlo con el manejo nativo de excepciones de C++, si eso es lo que buscas.

Editar: Microsoft C++ puede convertir una excepción estructurada en una excepción de C++. Que fue activado por defecto en VC++ 6. Esto no sucede por defecto con nuevos compiladores, pero estoy bastante seguro de que con un poco de espeleología, podría volver a encenderla.

Es cierto que cuando esto sucede, se queda sin espacio en la pila. Esa es parte de por qué mencioné el manejo de excepciones vectorizadas. Cada subproceso obtiene su propia pila, y un controlador de excepción vectorial puede ejecutarse en una secuencia separada desde donde se lanzó la excepción. Incluso SEH, sin embargo, puede manejar una excepción de desbordamiento de pila; solo tiene que generar manualmente un hilo para hacer la mayor parte del trabajo.

+0

"Normalmente" no puedes? ¿Cuándo * puedes * atrapas un desbordamiento de pila con manejo de excepciones nativo de C++? –

+0

Depende de lo que quiere decir con "C++ nativo", supongo. O el C++ que tienes en la máquina, o C++ estándar. –

+2

no su totalidad que sea fácil con ventanas ... porque una vez que haya atrapado que es necesario recordar que no tiene ningún espacio de pila;) – Goz

10

En realidad no hay portátil manera de hacerlo. Una función recursiva fuera de control generalmente causará un acceso de memoria no válido cuando intente asignar un marco de pila más allá del espacio de direcciones de la pila. Por lo general, esto bloqueará su programa con una falla de segmentación/violación de acceso, según el sistema operativo. En otras palabras, no arrojará una excepción de C++ que el lenguaje pueda manejar de manera estándar.

+1

Tengo dos problemas con esto: 1) SIEMPRE no importa si es portátil. 2) un desbordamiento de pila puede ocurrir más a menudo que simplemente estar "fuera de control". Ninguno de los dos vale la pena un voto negativo, pero no sé si esta respuesta es tan útil como lo indican los votos (no es que la mía sea mejor). –

1

En Windows puede utilizar el manejo de excepciones estructurado (SEH), con __try y __except palabras clave para instalar su propia rutina de gestión de excepciones que pueden atrapar los desbordamientos de pila, el acceso violación, etc etc.

Es bastante limpio para evitar de Windows 'diálogo de bloqueo predeterminado, y reemplazarlo por el suyo, si es necesario.

+0

tener cuidado al usar este enfoque de "manejar" la excepción manteniendo ir como si el fallo no fue así. al hacer eso, invitas a un mundo de dolor. – asveikau

+0

Es cierto. Debería haberlo señalado. Siempre he terminado después de todos modos. – Macke

-1

usted tiene que saber siempre un nivel de su recursividad y comprobar si es mayor que algún umbral.El nivel máximo (umbral) se calcula por la relación del tamaño de la pila dividida por la memoria que requiere una llamada recursiva.

La memoria requerida una llamada recursiva es la memoria para todos los argumentos de la función más la memoria para todas las variables locales más la memoria para la dirección de retorno + algunos bytes (aproximadamente 4-8).

0

Por supuesto, puede evitar el problema de recursividad convirtiéndolo en un bucle.

No estoy seguro si conoce esto, pero cualquier solución recursiva se puede traducir a una solución basada en bucle, y viceversa. Por lo general, es conveniente utilizar una solución basada en bucle porque es más fácil de leer y comprender.

Independientemente del uso de recursividad o bucle, debe asegurarse de que la condición de salida esté bien definida y siempre se golpeará.

+0

Estoy totalmente en desacuerdo con su segundo párrafo. Con mucha frecuencia, la solución recursiva es más fácil de leer y comprender, mientras que la solución basada en bucles necesita manejo de pila que complica las cosas. Atravesar un árbol binario es simple recursivamente (proceso left_child; process right_child;), y es más difícil de expresar iterativamente. (Por desgracia, la repetición es por lo general demostró en los problemas de juguete, como los factoriales de calcular, que se hacen mejor en un bucle.) –

+0

Si bien es cierto que cualquier fórmula recursiva se puede convertir en una serie de bucles, que * no * significa que sea una cosa fácil de hacer Eche un vistazo a la función de Ackermann, por ejemplo. –

5

No hay una manera portátil. Sin embargo, hay algunas soluciones no portables.

En primer lugar, como otros han mencionado, no estándar de Windows proporciona un marco __try y __except llamada Structured Exeption Handling (your specific answer está en la base de conocimientos).

En segundo lugar, alloca - si se aplica correctamente - puede decir si la pila está a punto de desbordarse:

bool probe_stack(size_t needed_stack_frame_size) 
{ 
    return NULL != alloca(needed_stack_frame_size); 
}; 

me gusta este enfoque, porque al final de probe_stack, la memoria asignada alloca se libera y disponible para su uso. Desafortunadamente, solo unos pocos sistemas operativos implementan correctamente el alloca. alloca nunca devuelve NULL en la mayoría de los sistemas operativos, lo que le permite descubrir que la pila ha desbordado con un choque espectacular.

En tercer lugar, los sistemas de tipo UNIX a menudo tienen un encabezado llamado ucontext.h con funciones para establecer el tamaño de la pila (o, de hecho, para encadenar varias pilas). Puede hacer un seguimiento de dónde se encuentra en la pila y determinar si está a punto de desbordarse. Windows viene con capacidades similares a laCreateFiber.


A partir de Windows 8, Windows tiene un function specifically for this (GetCurrentThreadStackLimits)

+1

que asumen que el cierre del programa no se lleva a cabo a través de una señal que se puede coger y recuperarse de gracia, sino más bien para estar seguro que es más de un: "! Dah !! ¿qué estás haciendo !? dejar de parar!" Esto es probablemente algo que pueda mirar hacia arriba después, pero ya que estás aquí me puede pedir (en una pregunta para que, si lo desea): lo que ocurre con los recursos abiertos que tengo cuando tal evento ocurre con mi proceso? –

+0

Realmente no sé qué sucede, aunque dudo que se ejecuten los destructores. Hay una construcción __finally que puedes usar, a la Java. –

6

Incluso si usted puede hacer esto no portable, como pueda en Windows, sigue siendo una muy mala idea . La mejor estrategia es no desbordar la pila en primer lugar. Si necesita aislamiento de algún código que no controla, ejecute ese código en un proceso diferente y puede detectar cuándo se bloquea. Pero no desea hacer ese tipo de cosas en su propio proceso, porque no sabe qué tipo de corrupción desagradable del estado hará el código ofensivo, y eso lo hará inestable.

Hay un interesante, algo relacionado blog post de Raymond Chen de Microsoft acerca de por qué no debe intentar comprobar si hay punteros válidos en una aplicación de modo de usuario en Windows.

+0

punto/idea increíble acerca de ejecutar cosas inservibles en un proceso diferente. esta es probablemente la mejor publicación que he visto hasta ahora sobre este tema. –

9

No es una excepción per se, pero si lo que desea es ser capaz de limitar su consumo de pila para una cantidad fija, que podría hacer algo como esto:

#include <stdio.h> 

// These will be set at the top of main() 
static char * _topOfStack; 
static int _maxAllowedStackUsage; 

int GetCurrentStackSize() 
{ 
    char localVar; 
    int curStackSize = (&localVar)-_topOfStack; 
    if (curStackSize < 0) curStackSize = -curStackSize; // in case the stack is growing down 
    return curStackSize; 
} 

void MyRecursiveFunction() 
{ 
    int curStackSize = GetCurrentStackSize(); 
    printf("MyRecursiveFunction: curStackSize=%i\n", curStackSize); 

    if (curStackSize < _maxAllowedStackUsage) MyRecursiveFunction(); 
    else 
    { 
     printf(" Can't recurse any more, the stack is too big!\n"); 
    } 
} 

int main(int, char **) 
{ 
    char topOfStack; 
    _topOfStack = &topOfStack; 
    _maxAllowedStackUsage = 4096; // or whatever amount you feel comfortable allowing 

    MyRecursiveFunction(); 
    return 0; 
} 
+1

Clever :) (15 caracteres) – cwap

-1
If you use Visual C++ 
Goto C/C++ , Code Generation 
Choose "Both..." in "Basic Runtime Checks" 

A continuación, ejecute su aplicación ...

Cuestiones relacionadas