¿Deberíamos usar exit () en C?

Hay una pregunta 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 exit en algún lugar del código, no se llamarán a los destructores de objetos, por lo tanto, si, por ejemplo, un destructor estaba destinado a escribir datos en un archivo, esto no Sucede, porque el destructor no fue llamado.

Me interesó cómo está esta situación en C. ¿Se aplican cuestiones similares también en C? Pensé que ya 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 siguientes, que me parecen buenas para 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 el uso de 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); } 

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 causa la terminación normal del progtwig.

El Estándar también dice en 7.22.4.4/p4 que:

A continuación, se eliminan 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 regresa 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 progtwig. Otras rutas a la terminación del progtwig, como llamar a la función de cancelación, no necesitan cerrar todos los archivos correctamente.

Sin embargo, como se menciona en los comentarios a continuación, no puede asumir 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 se dice en 7.22.4.2/p2 La función atexit :

La función atexit registra la función apuntada por func , para ser llamada sin argumentos en la terminación normal del progtwig.

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

Sí, está bien usar exit en C.

Para garantizar todos los búferes y el cierre ordenado y ordenado, se recomienda utilizar esta función atexit , más información sobre esto aquí

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, siempre que se llame a exit , se ejecutará la función de cleanup , que puede albergar el cierre correcto, la limpieza de búferes, la memoria, etc.

No tiene constructores ni destructores, pero podría tener recursos (por ejemplo, archivos, flujos, sockets) y es importante cerrarlos correctamente. Un búfer no se puede escribir de forma sincrónica, por lo que salir del progtwig sin cerrar correctamente el recurso primero, podría provocar daños.

Usando exit() está bien

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

En un progtwig de un solo hilo, en el código que está escribiendo para implementar ese progtwig, usar exit() está bien. Mis progtwigs 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 ‘subprocesos’ como ‘bibliotecas’ requieren una reflexión cuidadosa.

Progtwigs roscados

Si un progtwig tiene varios subprocesos, usar exit() es una acción dramática que termina todos los subprocesos. Probablemente sea inapropiado salir del progtwig completo. Puede ser apropiado salir del hilo, informando un error. Si conoce el diseño del progtwig, tal vez esa salida unilateral esté permitida, pero en general no será aceptable.

Código de la biblioteca

Y esa cláusula de ‘conocimiento del diseño del progtwig’ también se aplica al código en las bibliotecas. Es muy raro que una función de biblioteca de propósito general llame a exit() . Estaría justificadamente molesto si una de las funciones estándar de la biblioteca de C no pudiera regresar solo debido a 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 devolver una indicación de error de alguna manera . Si está escribiendo código para ingresar a una biblioteca de propósito general, debe considerar la estrategia de manejo de errores de su código con cuidado. Debería encajar con las estrategias de manejo de errores de los progtwigs con los que se pretende utilizar, o el manejo de errores se puede configurar.

Tengo una serie de funciones de biblioteca (en un paquete con encabezado "stderr.h" , un nombre que pisa en hielo delgado) 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 de salida se implementan en términos de funciones no de salida, por supuesto, pero eso es un detalle de implementación interno.

Tengo muchas otras funciones de biblioteca, y muchas de ellas dependen del código "stderr.h" para el informe de errores. Esa es una decisión de diseño que tomé y es una con la que estoy de acuerdo. Pero cuando los errores se informan 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 del código principal en la función tienen que lidiar con las devoluciones de errores de forma segura: detectelas y transmita una indicación de error al código que llama.


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

Una razón para evitar la exit en otras 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 las excepciones incatchable.

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

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

Dependiendo de lo que esté haciendo, la salida puede ser la forma más lógica de salir de un progtwig en C. Sé que es muy útil para verificar que las cadenas de devoluciones de llamadas funcionen correctamente. Toma este ejemplo de callback 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, configuro todo para este sistema y luego espero en un bucle momentáneo (1). Es posible hacer una bandera global para salir del bucle while en su lugar, pero esto es simple y hace lo que tiene que hacer. Si está tratando con búferes abiertos, como archivos y dispositivos, debe limpiarlos antes de cerrarlos para mantener la coherencia.

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