c

¿Deberíamos usar exit() en C?



(7)

Hay question sobre el uso de exit en C ++. La respuesta discute que no es una buena idea principalmente debido a RAII, por ejemplo, si se llama a la exit en algún lugar del código, no se llamarán los destructores de objetos, por lo tanto, si por ejemplo un destructor estaba destinado a escribir datos en el archivo, esto no sucede, porque no se llamó al destructor.

Me interesó cómo es esta situación en C. ¿Existen problemas similares aplicables también en C? Pensé que dado que en C no usamos constructores / destructores, la situación podría ser diferente en C. Entonces, ¿está bien usar exit en C?

He visto funciones como las que se muestran a continuación, que creo que está bien usar en algunos casos, pero estaba interesado si tenemos problemas similares en C con el uso de exit , como se describió anteriormente con C ++. (lo que haría que usar funciones como las siguientes no sea una buena idea).

void die(const char *message) { if(errno) { perror(message); } else { printf("ERROR: %s/n", message); } exit(1); }


Usar exit() está bien

Dos aspectos principales del diseño de código que aún no se han mencionado son ''threading'' y ''bibliotecas''.

En un programa de subproceso único, en el código que está escribiendo para implementar ese programa, usar exit() está bien. Mis programas lo usan rutinariamente cuando algo sale mal y el código no se va a recuperar.

Pero…

Sin embargo, llamar a exit() es una acción unilateral que no se puede deshacer. Es por eso que tanto ''threading'' como ''bibliotecas'' requieren una cuidadosa reflexión.

Programas roscados

Si un programa es multiproceso, entonces usar exit() es una acción dramática que termina todos los hilos. Probablemente sea inapropiado salir de todo el programa. Puede ser apropiado salir del hilo e informar un error. Si conoce el diseño del programa, tal vez esa salida unilateral sea permisible, pero en general, no será aceptable.

Código de la biblioteca

Y esa cláusula ''conocedor del diseño del programa'' se aplica también al código en las bibliotecas. Rara vez es correcto que una función de biblioteca de propósito general llame a exit() . Estaría molesto si una de las funciones estándar de la biblioteca C no regresara solo por un error. (Obviamente, las funciones como exit() , _Exit() , quick_exit() , abort() están destinadas a no regresar; eso es diferente.) Las funciones en la biblioteca C, por lo tanto, "no pueden fallar" o devuelven una indicación de error de alguna manera . Si está escribiendo código para ingresar a una biblioteca de propósito general, debe considerar cuidadosamente la estrategia de manejo de errores para su código. Debe encajar con las estrategias de manejo de errores de los programas con los que se pretende utilizar, o el manejo de errores puede configurarse.

Tengo una serie de funciones de biblioteca (en un paquete con el encabezado "stderr.h" , un nombre que pisa el hielo fino) que están destinadas a salir cuando se usan para informar errores. Esas funciones salen por diseño. Hay una serie de funciones relacionadas en el mismo paquete que informan errores y no salen. Las funciones existentes se implementan en términos de funciones no existentes, por supuesto, pero ese es un detalle de implementación interna.

Tengo muchas otras funciones de biblioteca, y muchas de ellas dependen del código "stderr.h" para informar de errores. Esa es una decisión de diseño que tomé y con la que estoy de acuerdo. Pero cuando se informan los errores con las funciones que salen, limita la utilidad general del código de la biblioteca. Si el código llama a las funciones de informe de errores que no salen, entonces las rutas de código principales en la función tienen que lidiar con los retornos de error de manera sensata: detectarlos y transmitir una indicación de error al código de llamada.

El código para mi paquete de informe de errores está disponible en mi SOQ ( Questions) en GitHub como archivos stderr.c y stderr.h en el src/libsoq .


Dependiendo de lo que esté haciendo, salir puede ser la forma más lógica de salir de un programa en C. Sé que es muy útil para verificar que las cadenas de devoluciones de llamada funcionen correctamente. Tome este ejemplo de devolución de llamada que usé recientemente:

unsigned char cbShowDataThenExit( unsigned char *data, unsigned short dataSz,unsigned char status) { printf("cbShowDataThenExit with status %X (dataSz %d)/n", status, dataSz); printf("status:%d/n",status); printArray(data,dataSz); cleanUp(); exit(0); }

En el bucle principal, configuré todo para este sistema y luego espero en un bucle de tiempo (1). Es posible hacer una bandera global para salir del ciclo while, pero esto es simple y hace lo que debe hacer. Si está tratando con búferes abiertos como archivos y dispositivos, debe limpiarlos antes de cerrar para mantener la coherencia.


En lugar de abort() , la función exit() en C se considera una salida "elegante".

Desde C11 (N1570) 7.22.4.4/p2 La función de salida (énfasis mío):

La función de exit provoca la finalización normal del programa.

La Norma también dice en 7.22.4.4/p4 que:

A continuación, se vacían todas las secuencias abiertas con datos almacenados en búfer no escritos , se cierran todas las secuencias abiertas y se eliminan todos los archivos creados por la función tmpfile .

También vale la pena mirar los archivos 7.21.3 / p5:

Si la función main vuelve a su llamador original, o si se llama a la función de exit , todos los archivos abiertos se cierran (por lo tanto, todas las secuencias de salida se vacían) antes de la finalización del programa. No es necesario que todas las rutas de finalización del programa, como llamar a la función de cancelación, cierren todos los archivos correctamente.

Sin embargo, como se menciona en los comentarios a continuación, no puede suponer que cubrirá todos los demás recursos , por lo que es posible que deba recurrir a atexit() y definir devoluciones de llamada para su lanzamiento individualmente. De hecho, es exactamente lo que pretende hacer atexit() , como dice en 7.22.4.2/p2 La función atexit :

La función atexit registra la función señalada por func , que se llamará sin argumentos en la terminación normal del programa.

En particular, el estándar C no dice con precisión qué debería suceder con los objetos de duración de almacenamiento asignada (es decir, malloc() ), por lo que requiere que esté al tanto de cómo se hace en una implementación particular. Para un sistema operativo moderno orientado al host, es probable que el sistema se encargue de ello, pero aún así es posible que desee manejarlo usted mismo para silenciar los depuradores de memoria como Valgrind .


Es terrible en un gran proyecto cuando cualquier código puede salir a excepción de coredump. El rastreo es muy importante para mantener un servidor en línea.


No tiene constructores y destructores, pero podría tener recursos (por ejemplo, archivos, secuencias, sockets) y es importante cerrarlos correctamente. Un búfer no se pudo escribir de forma síncrona, por lo que salir del programa sin cerrar correctamente el recurso primero podría provocar daños.


Sí, está bien usar exit en C.

Para garantizar todos los buffers y el apagado ordenado y ordenado, se recomienda utilizar esta función atexit , más información sobre esto here

Un código de ejemplo sería así:

void cleanup(void){ /* example of closing file pointer and free up memory */ if (fp) fclose(fp); if (ptr) free(ptr); } int main(int argc, char **argv){ /* ... */ atexit(cleanup); /* ... */ return 0; }

Ahora, cada vez que se llama a exit , la función de cleanup se ejecutará, lo que puede albergar un apagado elegante, limpieza de buffers, memoria, etc.


Una razón para evitar exit en funciones que no sean main() es la posibilidad de que su código se pueda sacar de contexto. Recuerde, la salida es un tipo de flujo de control no local . Al igual que excepciones inalcanzables.

Por ejemplo, puede escribir algunas funciones de administración de almacenamiento que salgan en un error de disco crítico. Entonces alguien decide trasladarlos a una biblioteca. Salir de una biblioteca es algo que hará que el programa de llamada salga en un estado inconsistente para el que no esté preparado.

O puede ejecutarlo en un sistema integrado. No hay ningún lugar para salir, todo se ejecuta en un ciclo while(1) en main() . Puede que ni siquiera esté definido en la biblioteca estándar.