Cómo finalizar el código C ++


267

Me gustaría que mi código C ++ deje de ejecutarse si se cumple una determinada condición, pero no estoy seguro de cómo hacerlo. Entonces, en cualquier momento, si una ifdeclaración es verdadera, finalice el código de esta manera:

if (x==1)
{
    kill code;
}

98
Utiliza la lógica adecuada. En main()retorno de uso, en funciones use un valor de retorno adecuado o arroje una Excepción adecuada. No lo use exit()!
kay - SE es malvado

66
@JonathanLeffler: cuando se compila con NDEBUGdefinido, assertprobablemente se convertirá en un no-op, por lo que no debe confiar en él para más que detectar errores.
MvG

13
@jamesqf: un returnfrom main()es perfectamente lógico. Establece el código de salida informado por el programa para el sistema operativo, que puede ser útil en muchos casos (si su programa se puede usar como parte de un proceso más grande, por ejemplo).
AAT

11
Curiosamente, esta Q es un duplicado de esta de hace 5 años, cuya respuesta aceptada es bastante diferente: stackoverflow.com/questions/1116493/how-to-quit-ac-program Supongo que la comunidad tardó 5 años en aprender eso a ciegas llamar a std :: exit puede ser malo?
Brandin

55
Preguntas ¿Por qué el uso se exit()considera malo? - SO 25141737 y ¿Cómo salir de un programa C ++? - SO 1116493 ahora están cerrados como duplicados de este. El primero de ellos tiene una referencia al 'Software solo para accidentes' que podría valer la pena, aunque solo sea para estimular su pensamiento sobre cómo escribir un software robusto.
Jonathan Leffler

Respuestas:


431

Hay varias formas, pero primero debe comprender por qué la limpieza de objetos es importante y, por lo tanto, la razón std::exitestá marginada entre los programadores de C ++.

RAII y Desbobinado de pila

C ++ utiliza un idioma llamado RAII , que en términos simples significa que los objetos deben realizar la inicialización en el constructor y la limpieza en el destructor. Por ejemplo, la std::ofstreamclase [puede] abrir el archivo durante el constructor, luego el usuario realiza operaciones de salida en él y, finalmente, al final de su ciclo de vida, generalmente determinado por su alcance, se llama al destructor que esencialmente cierra el archivo y descarga cualquier contenido escrito en el disco.

¿Qué sucede si no llega al destructor para vaciar y cerrar el archivo? ¡Quién sabe! Pero posiblemente no escribirá todos los datos que se suponía que debía escribir en el archivo.

Por ejemplo, considere este código

#include <fstream>
#include <exception>
#include <memory>

void inner_mad()
{
    throw std::exception();
}

void mad()
{
    auto ptr = std::make_unique<int>();
    inner_mad();
}

int main()
{
    std::ofstream os("file.txt");
    os << "Content!!!";

    int possibility = /* either 1, 2, 3 or 4 */;

    if(possibility == 1)
        return 0;
    else if(possibility == 2)
        throw std::exception();
    else if(possibility == 3)
        mad();
    else if(possibility == 4)
        exit(0);
}

Lo que sucede en cada posibilidad es:

  • Posibilidad 1: El retorno esencialmente deja el alcance de la función actual, por lo que sabe sobre el final del ciclo de vida de osllamar así a su destructor y realizar una limpieza adecuada al cerrar y vaciar el archivo al disco.
  • Posibilidad 2: Lanzar una excepción también se ocupa del ciclo de vida de los objetos en el alcance actual, haciendo así una limpieza adecuada ...
  • Posibilidad 3: ¡ Aquí el desbobinado de la pila entra en acción! Aunque se lanza la excepción inner_mad, el desbobinador pasará por la pila de mady mainpara realizar la limpieza adecuada, todos los objetos se destruirán correctamente, incluidos ptry os.
  • Posibilidad 4: Bueno, ¿aquí? exites una función de C y no es consciente ni compatible con los modismos de C ++. Que no se realice la limpieza de sus objetos, incluyendo osen el mismo ámbito. Por lo tanto, su archivo no se cerrará correctamente y, por esta razón, ¡es posible que el contenido nunca se escriba en él!
  • Otras posibilidades: Simplemente dejará el alcance principal, realizando una acción implícita return 0y, por lo tanto, tendrá el mismo efecto que la posibilidad 1, es decir, una limpieza adecuada.

Pero no esté tan seguro de lo que le acabo de decir (principalmente las posibilidades 2 y 3); continúe leyendo y descubriremos cómo realizar una limpieza adecuada basada en excepciones.

Posibles formas de terminar

Regreso de la principal!

Debe hacer esto siempre que sea posible; siempre prefiera regresar de su programa devolviendo un estado de salida adecuado de main.

La persona que llama de su programa, y ​​posiblemente el sistema operativo, puede querer saber si lo que su programa debía hacer se realizó con éxito o no. Por esta misma razón, debe devolver cero o EXIT_SUCCESSpara indicar que el programa finalizó correctamente y EXIT_FAILUREpara indicar que el programa finalizó sin éxito, cualquier otra forma de valor de retorno está definida por la implementación ( §18.5 / 8 ).

Sin embargo, puede ser muy profundo en la pila de llamadas, y devolver todo puede ser doloroso ...

[No] lanzar una excepción

Lanzar una excepción llevará a cabo la limpieza adecuada del objeto utilizando el desbobinado de la pila, llamando al destructor de cada objeto en cualquier ámbito anterior.

Pero aquí está el truco ! Está definido por la implementación si el desbobinado de la pila se realiza cuando una excepción lanzada no se maneja (mediante la cláusula catch (...)) o incluso si tiene una noexceptfunción en el medio de la pila de llamadas. Esto se establece en §15.5.1 [excepto.terminal] :

  1. En algunas situaciones, se debe abandonar el manejo de excepciones para técnicas de manejo de errores menos sutiles. [Nota: Estas situaciones son:

    [...]

    - cuando el mecanismo de manejo de excepciones no puede encontrar un controlador para una excepción lanzada (15.3), o cuando la búsqueda de un controlador (15.3) encuentra el bloque más externo de una función con una noexceptespecificación- que no permite la excepción (15.4), o [...]

    [...]

  2. En tales casos, se llama std :: terminate () (18.8.3). En la situación en la que no se encuentra un controlador coincidente, se define la implementación si la pila se desenrolla o no antes de llamar a std :: terminate () [...]

¡Entonces tenemos que atraparlo!

¡Lanza una excepción y atrapala en main!

Dado que las excepciones no detectadas pueden no realizar el desbobinado de la pila (y, en consecuencia, no realizarán la limpieza adecuada) , debemos detectar la excepción en main y luego devolver un estado de salida ( EXIT_SUCCESSo EXIT_FAILURE).

Entonces, una configuración posiblemente buena sería:

int main()
{
    /* ... */
    try
    {
        // Insert code that will return by throwing a exception.
    }
    catch(const std::exception&)  // Consider using a custom exception type for intentional
    {                             // throws. A good idea might be a `return_exception`.
        return EXIT_FAILURE;
    }
    /* ... */
}

[No] std :: salir

Esto no realiza ningún tipo de desbobinado de la pila, y ningún objeto vivo en la pila llamará a su respectivo destructor para realizar la limpieza.

Esto se aplica en §3.6.1 / 4 [basic.start.init] :

Terminar el programa sin abandonar el bloque actual (por ejemplo, llamando a la función std :: exit (int) (18.5)) no destruye ningún objeto con una duración de almacenamiento automática (12.4) . Si se llama a std :: exit para finalizar un programa durante la destrucción de un objeto con una duración de almacenamiento estático o de subprocesos, el programa tiene un comportamiento indefinido.

Piénsalo ahora, ¿por qué harías tal cosa? ¿Cuántos objetos has dañado dolorosamente?

Otras alternativas [como malas]

Hay otras formas de finalizar un programa (que no sean fallas) , pero no se recomiendan. Solo en aras de una aclaración, se presentarán aquí. Observe cómo la finalización normal del programa no significa el desenrollado de la pila, sino un estado correcto para el sistema operativo.

  • std::_Exit provoca una terminación normal del programa, y ​​eso es todo.
  • std::quick_exitprovoca una finalización normal del programa y llama a los std::at_quick_exitcontroladores, no se realiza ninguna otra limpieza.
  • std::exitprovoca una finalización normal del programa y luego llama a los std::atexitcontroladores. Se realizan otros tipos de limpieza, como llamar a destructores de objetos estáticos.
  • std::abortprovoca una terminación anormal del programa, no se realiza ninguna limpieza. Esto debería llamarse si el programa terminó de una manera muy, muy inesperada. No hará nada más que indicarle al sistema operativo sobre la terminación anormal. Algunos sistemas realizan un volcado de núcleo en este caso.
  • std::terminatellama a las llamadas std::terminate_handlerque std::abortpor defecto.

25
Esto es bastante informativo: en particular, nunca me di cuenta de que lanzar una excepción que no se maneja en ningún lado (por ejemplo: algunos newlanzamientos std::bad_allocy su programa olvidó detectar esa excepción en cualquier lugar) no desenrollará correctamente la pila antes de finalizar. Esto parece tonto a mí: que hubiera sido fácil esencialmente envolver la llamada a mainun trivial try{- }catch(...){}bloque que aseguraría pila desenrollar se hace correctamente en estos casos, sin costo (y me refiero a: programas que no utilizan este no pagará multa). ¿Hay alguna razón en particular para que esto no se haga?
Marc van Leeuwen

12
@MarcvanLeeuwen una posible razón es la depuración: desea entrar en el depurador tan pronto como se produzca una excepción no controlada. Desenrollar la pila y hacer la limpieza borraría el contexto de la causa del bloqueo que desea depurar. Si no hay un depurador presente, sería mejor volcar el núcleo para que se pueda realizar un análisis post mortem.
Hugh Allen

11
A veces mi navegador falla (culpo al flash) y consume varios gigabytes de RAM y mi sistema operativo voltea páginas en el disco duro haciendo que todo sea más lento. Cuando cierro el navegador, se desenrolla correctamente la pila, lo que significa que todos esos gigabytes de RAM se leen desde el disco duro y se copian en la memoria para liberarlos, lo que lleva aproximadamente un minuto. Desearía que hubieran usado en su std::abortlugar para que el sistema operativo pueda liberar toda la memoria, los sockets y los descriptores de archivos sin cambiar por un minuto.
nwp

2
@nwp, entiendo el sentimiento. Pero matar instantáneamente puede dañar archivos, no guardar mis pestañas más recientes, etc. :)
Paul Draper

2
@PaulDraper Ciertamente no lo consideraría tolerable si un navegador no pudiera restaurar las pestañas que había abierto antes de una pérdida de energía. Por supuesto, si acabara de abrir una pestaña y no hubiera tenido tiempo de guardarla, se perdería. Pero aparte de eso, digo que no hay excusa para perderlo.
kasperd

61

Como Martin York mencionó, la salida no realiza la limpieza necesaria como lo hace el retorno.

Siempre es mejor usar return en el lugar de salida. En caso de que no esté en main, donde quiera salir del programa, regrese primero a main.

Considere el siguiente ejemplo. Con el siguiente programa, se creará un archivo con el contenido mencionado. Pero si return es comentado y no comentado exit (0), el compilador no le asegura que el archivo tendrá el texto requerido.

int main()
{
    ofstream os("out.txt");
    os << "Hello, Can you see me!\n";
    return(0);
    //exit(0);
}

No solo esto, tener múltiples puntos de salida en un programa hará que la depuración sea más difícil. Use exit solo cuando pueda justificarse.


2
¿Qué recomiendas para lograr este comportamiento en un programa un poco más grande? ¿Cómo siempre regresas a main limpiamente si en algún lugar más profundo del código se activa una condición de error que debería salir del programa?
Janusz el

55
@ Janusz, en ese caso, puede usar / lanzar excepciones, si no devuelve un valor predefinido, es decir, tener un valor de retorno de la función, como por ejemplo devolver 0 cuando tiene éxito, 1 en caso de falla, pero continuar la ejecución , -1 en caso de falla y salir del programa. Basado en el valor de retorno de la función, si eso es una falla, solo regrese de main después de realizar más actividades de limpieza. Finalmente, use la salida juiciosamente, no quiero evitarlo.
Narendra N

1
@NarendraN, la "limpieza necesaria" es vaga: el sistema operativo se encargará (Windows / Linux) de que la memoria y los identificadores de archivos se liberen correctamente. En cuanto a la salida del archivo "faltante": si insiste en que esto podría ser un problema real, consulte stackoverflow.com/questions/14105650/how-does-stdflush-work Si tiene una condición de error, el registro adecuado le indica que su programa alcanzó un estado indefinido y puede establecer un punto de interrupción justo antes de su punto de registro. ¿Cómo eso dificulta la depuración?
Markus

2
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

para uso moderno de C ++ en su return (EXIT_SUCCESS);lugarreturn(0)
Jonas Stein

39

Llama a la std::exitfunción.   


2
¿Qué destructores de objetos se llaman cuando llamas a esa función?
Rob Kennedy el

37
exit () no regresa. Por lo tanto, no se puede desenrollar la pila. Ni siquiera los objetos globales no se destruyen. Pero las funciones registradas con atexit () serán llamadas.
Martin York

16
No llame exit()cuando el código de su biblioteca se esté ejecutando dentro de mi proceso de host; este último se cerrará en el medio de la nada.
Sharptooth

55
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

23

La gente dice "salida de llamada (código de retorno)", pero esta es una mala forma. En programas pequeños está bien, pero hay una serie de problemas con esto:

  1. Terminarás teniendo múltiples puntos de salida del programa
  2. Hace que el código sea más complicado (como usar goto)
  3. No puede liberar memoria asignada en tiempo de ejecución

Realmente, la única vez que debe salir del problema es con esta línea en main.cpp:

return 0;

Si está utilizando exit () para manejar errores, debe conocer las excepciones (y las excepciones de anidación), como un método mucho más elegante y seguro.


9
En un entorno de subprocesos múltiples, una excepción lanzada en un subproceso diferente no se manejará a través de main (): se necesita cierta comunicación manual entre subprocesos antes de que caduque el subproceso subordinado.
Steve Gilham

3
1. y 2. dependen del programador, con un registro adecuado esto no es un problema ya que la ejecución se detiene para siempre. En cuanto a 3: eso es simplemente incorrecto, el sistema operativo liberará la memoria, tal vez excluyendo los dispositivos integrados / en tiempo real, pero si está haciendo eso, probablemente sepa lo que hace.
Markus

1
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

1
Sin embargo, si se produjo un error, no debe devolver 0. Debe devolver 1 (o posiblemente algún otro valor, pero 1 siempre es una opción segura).
Kef Schecter

14

return 0;ponlo donde quieras dentro int main()y el programa se cerrará inmediatamente.


the-nightman, @ evan-carslake, y todos los demás, también agregaría que esta pregunta SO # 36707 da una discusión sobre si una declaración de devolución debería ocurrir en cualquier parte de la rutina. Esta es una solución; Sin embargo, dependiendo de la situación específica, no diría que esta es la mejor solución.
localhost

1
OP no dijo nada al respecto, por lo que creo que es bastante precipitado suponer que el código estaba destinado a estar dentro de la función main.
Marc van Leeuwen

@localhost Una declaración de devolución es completamente opcional en cualquier situación, bajo cualquier condición. Sin embargo, int main()es diferente. El programa usa int main()para comenzar y terminar. No hay otra forma de hacerlo. El programa se ejecutará hasta que devuelva verdadero o falso. Casi todo el tiempo devolverá 1 o verdadero, para indicar que el programa se ha cerrado correctamente (por ejemplo: podría usar falso si no puede liberar memoria o cualquier otra razón). Dejar que el programa se ejecute terminado siempre es una mala idea, por ejemplo:int main() { int x = 2; int foo = x*5; std::cout << "blah"; }
Evan Carslake

@EvanCarslake Entendido, si notaste un comentario que publiqué en otra parte para esta pregunta, estoy familiarizado con esto int main; sin embargo, no siempre es una buena idea tener múltiples declaraciones de retorno en una rutina principal. Una advertencia potencial es mejorar la legibilidad del código; sin embargo, usar algo como una bandera booleana que cambia de estado para evitar que ciertas secciones de código se ejecuten en ese estado de aplicación. Personalmente, creo que una declaración de devolución común hace que la mayoría de las aplicaciones sean más legibles. Por último, preguntas sobre la limpieza de memoria y el cierre de objetos de E / S correctamente antes de la salida.
localhost

2
@EvanCarslake no regresa truea mainindicar una terminación correcta. Debe devolver cero o EXIT_SUCCESS(o, si lo desea false, que se convertiría implícitamente a cero) para indicar la terminación normal. Para indicar una falla, puede regresar EXIT_FAILURE. Cualquier otro significado del código está definido por la implementación (en sistemas POSIX significaría un código de error real).
Ruslan

11

El programa finalizará cuando el flujo de ejecución llegue al final de la función principal.

Para terminarlo antes, puede usar la función de salida (int status), donde status es un valor devuelto a lo que inició el programa. 0 normalmente indica un estado sin error


2
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

11

O devuelve un valor de usted maino usa la exitfunción. Ambos toman una int. Realmente no importa qué valor devuelva a menos que tenga un proceso externo que esté atento al valor de retorno.


3
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

11

Si tiene un error en algún lugar profundo del código, arroje una excepción o configure el código de error. Siempre es mejor lanzar una excepción en lugar de establecer códigos de error.


2
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

9

Generalmente usaría el exit()método con un estado de salida apropiado .

Cero significaría una carrera exitosa. Un estado distinto de cero indica que se ha producido algún tipo de problema. Este código de salida lo utilizan los procesos primarios (por ejemplo, los scripts de shell) para determinar si un proceso se ha ejecutado correctamente.


1
usar exit PUEDE significar que tiene un problema de diseño. Si un programa funciona correctamente, debería terminar cuando sea principal return 0;. Supongo que exit()es así assert(false);y, por lo tanto, debe usarse solo en el desarrollo para problemas de captura temprana.
CoffeDeveloper

2
Tenga en cuenta que esta respuesta se fusionó con la pregunta ¿Cómo salir de un programa C ++? - SO 1116493 . Esta respuesta fue escrita aproximadamente 6 años antes de que se hiciera esta pregunta.
Jonathan Leffler

7

Más allá de llamar a exit (error_code), que llama a los controladores atexit, pero no a los destructores RAII, etc., cada vez uso más excepciones.

Más y más mi programa principal se ve así

int main(int argc, char** argv) 
{
    try {
        exit( secondary_main(argc, argv );
    }
    catch(...) {
        // optionally, print something like "unexpected or unknown exception caught by main"
        exit(1);
    }
}

donde secundario_principal donde se colocan todas las cosas que originalmente se colocaron, es decir, el principal original se renombra secundario_principal, y se agrega el código auxiliar principal anterior. Esto es solo un detalle, por lo que no hay demasiado código entre la bandeja y la captura en main.

Si lo desea, capture otros tipos de excepción.
Me gusta mucho detectar tipos de error de cadena, como std :: string o char *, e imprimirlos en el controlador catch en main.

El uso de excepciones como esta permite al menos llamar a los destructores RAII, para que puedan hacer la limpieza. Que puede ser agradable y útil.

En general, el manejo de errores C - salida y señales - y el manejo de errores C ++ - excepciones try / catch / throw - juegan juntos de manera inconsistente en el mejor de los casos.

Entonces, donde detectas un error

throw "error message"

o algún tipo de excepción más específico.


Por cierto: soy consciente de que usar un tipo de datos como una cadena, que puede implicar la asignación dinámica de memoria, no es una buena idea dentro o alrededor de un controlador de excepciones para excepciones que pueden estar relacionadas con la falta de memoria. Las constantes de cadena de estilo C no son un problema.
Krazy Glew

1
No tiene sentido llamar exita su programa. Ya que estás dentro main, puedes simplemente return exitCode;.
Ruslan

-1

Si su declaración if está en Loop, puede usar

 break; 

Si desea escapar de algún código y continuar en bucle Use:

Seguir;

Si su declaración if no está en Loop, puede usar:

 return 0;

Or 




  exit();

-2

Amigo ... la exit()función se define en stdlib.h

Por lo tanto, debe agregar un preprocesador.

Poner include stdlib.hen la sección del encabezado

Luego use exit();donde quiera pero recuerde poner un número entero en el paréntesis de salida.

por ejemplo:

exit(0);

-2

Si la condición que estoy probando es realmente una mala noticia, hago esto:

*(int*) NULL= 0;

Esto me da un buen coredump desde donde puedo examinar la situación.


44
Esto puede optimizarse, ya que causa un comportamiento indefinido. De hecho, el compilador puede borrar toda la rama de ejecución que tenga dicha declaración.
Ruslan

-2

Para romper una condición, use el retorno (0);

Entonces, en su caso sería:

    if(x==1)
    {
        return 0;
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.