¿Debería un programa C ++ capturar todas las excepciones y evitar que las excepciones surjan más allá de main ()?

29

Una vez me aconsejaron que un programa C ++ debería capturar todas las excepciones. El razonamiento dado en ese momento era esencialmente que los programas que permiten que surjan excepciones fuera de main()entrar en un extraño estado zombie. Me dijeron esto hace varios años y, en retrospectiva, creo que el fenómeno observado se debió a la larga generación de vertederos de núcleos excepcionalmente grandes del proyecto en cuestión.

En ese momento, esto parecía extraño pero convincente. Era totalmente absurdo que C ++ "castigara" a los programadores por no detectar todas las excepciones, pero la evidencia ante mí parecía respaldar esto. Para el proyecto en cuestión, los programas que arrojaron excepciones no detectadas parecían entrar en un extraño estado zombie, o como sospecho que la causa era ahora, un proceso en medio de un volcado de núcleo no deseado es inusualmente difícil de detener.

(Para cualquiera que se pregunte por qué esto no era más obvio en ese momento: el proyecto generó una gran cantidad de resultados en múltiples archivos de múltiples procesos que efectivamente ocultaron cualquier tipo de aborted (core dumped)mensaje y, en este caso particular, el examen post mortem de los volcados del núcleo no fue Es una técnica de depuración importante, por lo que no se pensó mucho en los volcados del núcleo. Los problemas con un programa generalmente no dependían del estado acumulado de muchos eventos a lo largo del tiempo por un programa de larga duración, sino más bien las entradas iniciales a un programa de corta duración (< 1 hora), por lo que era más práctico volver a ejecutar un programa con las mismas entradas de una compilación de depuración o en un depurador para obtener más información).

Actualmente, no estoy seguro de si existe alguna ventaja o desventaja importante de capturar excepciones con el único fin de evitar que las excepciones se vayan main().

La pequeña ventaja que se me ocurre al permitir que surjan excepciones pasadas main()es que hace que el resultado std::exception::what()se imprima en el terminal (al menos con los programas compilados con gcc en Linux). Por otro lado, esto es trivial de lograr al capturar en su lugar todas las excepciones derivadas std::exceptione imprimir el resultado std::exception::what()y, si es deseable imprimir un mensaje de una excepción que no se deriva de std::exceptionél, debe capturarse antes de partir main()para imprimir. el mensaje.

La desventaja modesta en la que puedo pensar para permitir que surjan excepciones main()es que se pueden generar volcados de núcleos no deseados. Para un proceso que utiliza una gran cantidad de memoria, esto puede ser una molestia y controlar el comportamiento de volcado de núcleo de un programa requiere llamadas a funciones específicas del sistema operativo. Por otro lado, si se desea un volcado de núcleo y una salida, esto se podría lograr en cualquier momento llamando std::abort()y una salida sin volcado de núcleo se puede lograr en cualquier momento llamando std::exit().

Como anécdota, no creo haber visto nunca el what(): ...mensaje predeterminado impreso por un programa ampliamente distribuido al fallar.

¿Cuáles son, si los hay, los argumentos fuertes a favor o en contra de permitir que las excepciones de C ++ surjan main()?

Editar: Hay muchas preguntas generales sobre el manejo de excepciones en este sitio. Mi pregunta es específicamente sobre las excepciones de C ++ que no se pueden manejar y que han llegado hasta el final main(); tal vez se pueda imprimir un mensaje de error, pero es un error de detención que se muestra inmediatamente.

Praxeolítico
fuente
@gnat Mi pregunta es un poco más específica. Se trata de excepciones de C ++ que no se pueden manejar en lugar del manejo de excepciones en cualquier lenguaje de programación bajo ninguna circunstancia.
Praxeolítico
Para programas de subprocesos múltiples, las cosas pueden ser más complejas o más exóticas (excepciones wrt)
Basile Starynkevitch
1
Los tipos de software para el Ariane 5 desde luego desearían haber cogido todas las excepciones durante la primera puesta en marcha ...
Eric Torres

Respuestas:

25

Un problema al permitir que las excepciones pasen de main es que el programa terminará con una llamada a la std::terminateque debe llamar el comportamiento predeterminado std::abort. Solo se define la implementación si el desbobinado de la pila se realiza antes de llamar terminatepara que su programa pueda finalizar sin llamar a un solo destructor Si tiene algún recurso que realmente necesitaba ser restaurado por una llamada de destructor, está en apuros ...

erlc
fuente
12
Si tiene algún recurso que realmente necesitaba ser restaurado por una llamada de destructor, está en un aprieto ... => Dado que (desafortunadamente) C ++ es bastante propenso a bloquearse, y eso sin mencionar que el sistema operativo podría decidir matar su programa en cualquier momento (OOM killer en Unix, por ejemplo), su programa (y su entorno) deben adaptarse para soportar bloqueos.
Matthieu M.
@MatthieuM. ¿Estás diciendo que los destructores no son un buen lugar para la limpieza de recursos que debería ocurrir incluso durante un bloqueo?
Praxeolítico
66
@Praxeolitic: Estoy diciendo que no todos los bloqueos desenrollan la pila, por ejemplo, std::abortno lo hace y, por lo tanto, las afirmaciones fallidas tampoco. También vale la pena señalar que en los sistemas operativos modernos, el sistema operativo en sí limpiará muchos recursos (memoria, identificadores de archivos, ...) que están vinculados a la ID del proceso. Finalmente, también estaba insinuando "Defensa en profundidad": no es confiable esperar que todos los demás procesos sean a prueba de errores y siempre liberarán los recursos que adquirieron (sesiones, terminar de escribir archivos, ...) it ...
Matthieu M.
3
@Praxeolitic No, su software no debe corromper los datos durante una falla de energía. Y si puede administrar eso, también puede administrar no dañar los datos después de una excepción no controlada.
user253751
1
@Praxeolitic: En realidad, esto existe. Querrás escuchar el WM_POWERBROADCASTmensaje. Esto solo funciona si su computadora funciona con baterías (si está usando una computadora portátil o un UPS).
Brian
28

La razón principal para no dejar escapar las excepciones maines porque, de lo contrario, pierde toda posibilidad de controlar cómo se informa el problema a sus usuarios.

Para un programa que no está destinado a ser utilizado durante mucho tiempo o distribuido ampliamente, puede ser aceptable que se informen errores inesperados de cualquier manera que el sistema operativo decida hacerlo (por ejemplo, mostrando un diálogo de error directo en Windows )

Para los programas que vende, o que son proporcionados al público en general por una organización que tiene una reputación que mantener, generalmente es una mejor idea informar de una manera agradable que encontró un problema inesperado y tratar de ahorrar la mayor parte de datos del usuario como sea posible. No perder el trabajo de medio día de su usuario y no fallar inesperadamente, pero cerrar de forma semi-elegante generalmente es mucho mejor para su reputación comercial que la alternativa.

Bart van Ingen Schenau
fuente
¿Está seguro de que el comportamiento predeterminado de una excepción de C ++ que deja main() tiene mucho que ver con el sistema operativo? El sistema operativo puede no saber nada de C ++. Supongo que está determinado por el código que el compilador inserta en algún lugar del programa.
Praxeolítico
55
@Praxeolitic: es más que el sistema operativo establece convenciones y el compilador genera código para cumplir con esas convenciones cuando un programa finaliza inesperadamente. La conclusión es que la terminación inesperada del programa debe evitarse siempre que sea razonablemente posible.
Bart van Ingen Schenau
Solo pierde el control dentro del alcance de std C ++. Debería estar disponible una forma específica de implementación para manejar tales "bloqueos". C ++ no suele ejecutarse en el vacío.
Martin Ba
Ese diálogo de error en su cara se puede configurar con./desc. En el registro para lo que vale, pero necesitaría el control del registro para hacer esto, y la mayoría de los programas no se están ejecutando en modo quiosco (habiendo asumido el control el sistema operativo).
PerryC
11

TL; DR : ¿Qué dice la especificación?


Un desvío técnico ...

Cuando se produce una excepción y ningún controlador está listo para ello:

  • es la implementación definida si la pila está desenrollada o no
  • std::terminate se llama, que por defecto aborta
  • dependiendo de la configuración de su entorno, el aborto puede o no dejar un informe de falla

Este último puede ser útil para errores muy poco frecuentes (porque reproducirlos es un proceso que desperdicia tiempo).


Si atrapar todas las excepciones o no es, en última instancia, una cuestión de especificación:

  • ¿Se especifica cómo informar los errores del usuario? (valor no válido, ...)
  • ¿Se especifica cómo informar errores funcionales? (falta el directorio de destino, ...)
  • ¿Se especifica cómo informar errores técnicos? (afirmación disparando, ...)

Para cualquier programa de producción, esto debe especificarse y usted debe seguir la especificación (y tal vez argumentar que se debe cambiar).

Para los programas agrupados rápidamente para ser utilizados solo por personas técnicas (usted, sus compañeros de equipo), tampoco está bien. Recomiendo dejar que se bloquee y configurar el entorno para obtener un informe o no, dependiendo de sus necesidades.

Matthieu M.
fuente
1
Esta. Los factores decisivos wrt. Están básicamente fuera del alcance de C ++.
Martin Ba
4

Una excepción que detecta le da la oportunidad de imprimir un buen mensaje de error o incluso tratar de recuperarse del error (posiblemente simplemente reiniciando la aplicación).

Sin embargo, en C ++ una excepción no contiene información sobre el estado del programa cuando se lanzó. Si lo detecta, todo ese estado se olvida, mientras que si deja que el programa se bloquee, el estado generalmente sigue ahí y se puede leer desde el volcado del programa, lo que facilita la depuración.

Entonces es una compensación.

Sebastian Redl
fuente
4

En el momento en que sepa que tiene que abortar, continúe y llame std::terminatepara reducir cualquier daño adicional.

Si sabe que puede relajarse con seguridad, hágalo en su lugar. Recuerde que el desbobinado de la pila no está garantizado cuando nunca se detecta una excepción, así que atrape y vuelva a lanzar.

Si puede informar / registrar el error de manera segura mejor de lo que el sistema lo hará solo, continúe.
Pero asegúrese de no empeorar las cosas sin darse cuenta al hacerlo.

A menudo es demasiado tarde para guardar datos cuando detecta un error irrecuperable, aunque depende del error específico.
De todos modos, si su programa está escrito para una recuperación rápida, simplemente matarlo podría ser la mejor manera de terminarlo, incluso si es solo un apagado normal.

Deduplicador
fuente
1

Chocar con gracia es algo bueno la mayor parte del tiempo, pero hay compensaciones. A veces es bueno chocar. Debo mencionar que estoy pensando principalmente en depurar a gran escala. Para un programa simple, aunque esto podría ser útil, no es tan útil como lo es con un programa muy complejo (o varios programas complejos que interactúan).

No desea bloquearse en público (aunque esto es realmente inevitable: los programas se bloquean, y un programa que no se bloquea verdaderamente matemáticamente verificable no es de lo que estamos hablando aquí). Piensa en BSODing de Bill Gates en medio de una demostración, ¿mal, verdad? No obstante, podemos tratar de descubrir por qué nos estrellamos y no volver a estrellarnos de la misma manera.

Encendí una función de Informe de errores de Windows que crea volcados de memoria locales en excepciones no controladas. Funciona de maravilla si tiene los archivos de símbolos asociados con su compilación (bloqueada). Curiosamente, porque configuré esta herramienta, quiero bloquear más , porque aprendo de cada bloqueo. Entonces puedo arreglar los errores y fallar menos.

Entonces, por ahora, quiero que mis programas lleguen a la cima y se bloqueen. En el futuro, podría querer comer con gracia todas mis excepciones, pero no si puedo hacer que funcionen para mí.

Puede leer más acerca de los volcados por caída locales aquí si está interesado: Recopilar volcados en modo de usuario

PerryC
fuente
-6

En última instancia, si una excepción aparece más allá de main (), bloqueará su aplicación y, en mi opinión, una aplicación nunca debería bloquearse. Si está bien bloquear una aplicación en un lugar, ¿por qué no en cualquier lugar? ¿Por qué molestarse con un manejo excepcional? (Sarcasmo, realmente no sugiere esto ...)

Es posible que tenga un intento / captura global que imprime un mensaje elegante que le dice al usuario que se ha producido un error irrecuperable y que necesita enviar un correo electrónico a TI al respecto o algo similar, pero el bloqueo es completamente no profesional e inaceptable. Es trivial prevenir y puede informar a un usuario sobre lo que acaba de suceder y cómo solucionarlo para que no vuelva a suceder.

Estrellarse es estrictamente hora de aficionados.

Roger Hill
fuente
8
Entonces, ¿mejor fingir que todo funciona correctamente y sobrescribir todo el almacenamiento permanente con galimatías en su lugar?
Deduplicador
1
@Deduplicator: No: siempre puedes detectar la excepción y manejarla.
Giorgio
55
Si sabe cómo manejarlo, probablemente lo manejará mucho antes de que lo haga main. Si no sabes cómo manejarlo, fingir que lo haces es obviamente un error realmente horrible.
Deduplicador
8
pero el bloqueo es completamente no profesional e inaceptable => pasar tiempo trabajando en funciones inútiles no es profesional: el bloqueo solo importa si es importante para el usuario final, si no es así, dejar que se bloquee (y obtener un informe de bloqueo, con un volcado de memoria) Es más económico.
Matthieu M.
Matthieu M. si realmente le toma tanto esfuerzo registrar un error, guardar los archivos abiertos y pintar un mensaje amistoso en la pantalla, no sé qué decir. Si crees que es inútil fallar con gracia, probablemente deberías hablar con quien esté haciendo soporte técnico para tu código.
Roger Hill