declaración de retorno vs salida () en main ()

197

¿Debo usar exit()o solo returndeclaraciones en main()? Personalmente, estoy a favor de las returndeclaraciones porque siento que es como leer cualquier otra función y el control de flujo cuando leo el código es fluido (en mi opinión). E incluso si quiero refactorizar la main()función, tener returnparece ser una mejor opción que exit().

¿Hace exit()algo especial que returnno lo hace?

Srikanth
fuente

Respuestas:

277

En realidad, no es una diferencia, pero es sutil. Tiene más implicaciones para C ++, pero las diferencias son importantes.

Cuando llamo , se llamarán returna los main()destructores para mis objetos de ámbito local. Si llamo exit(), no se llamará a destructor para mis objetos localmente alcanzados. Vuelve a leer eso. exit() no vuelve . Eso significa que una vez que lo llamo, no hay "backsies". Los objetos que haya creado en esa función no serán destruidos. A menudo esto no tiene implicaciones, pero a veces lo hace, como cerrar archivos (¿seguramente desea que todos sus datos se vacíen en el disco?).

Tenga en cuenta que los staticobjetos se limpiarán incluso si llama exit(). Finalmente tenga en cuenta que si usa abort(), no se destruirán objetos. Es decir, no se llamarán a sus destructores objetos globales, objetos estáticos ni objetos locales.

Proceda con precaución al favorecer la salida sobre el retorno.

http://groups.google.com/group/gnu.gcc.help/msg/8348c50030cfd15a

Memoria libre
fuente
1
abort () sale con una condición de error (código de salida distinto de cero) y puede ser incluso un núcleo. Si necesita salir sin llamar a destructores estáticos, use _exit.
77
@ Mike: hay una diferencia entre los búferes de archivos de la biblioteca C y los objetos de flujo de archivos C ++. exit (), que forma parte de la biblioteca C, se diseñó para coordinarse y vaciar la primera, pero puede omitir la segunda: incluso el contenido estándar de C ++ fstream no se vacía en el disco (pruébalo, lo hice, falla con Linux / GCC), y obviamente no se puede esperar que los tipos definidos por el usuario que tienen E / S en búfer se vacíen.
Tony Delroy el
9
Nota: La declaración: ¡ no se llamará a ningún destructor para mis objetos de ámbito local! ya no es cierto para C ++ 11: - Los objetos asociados con el subproceso actual con la duración del almacenamiento de subprocesos se destruyen (solo C ++ 11). cplusplus.com/reference/cstdlib/exit
Ilendir
77
Significa thread_localque se llamará a los destructores de objetos. Los destructores de otros objetos locales todavía no se llaman. ideone.com/Y6Dh3f
HolyBlackCat
3
Por cierto, y solo para ser pedante y porque esta respuesta aún puede ser confusa para los lectores que usan C: Para C, el problema de exit()cerrar los archivos limpiamente es realmente incorrecto. El único momento en que los datos podrían no vaciarse es en el caso opuesto: es decir, si uno usa returndesde main()y ha llamado setbuf()o setvbuf()con un búfer declarado como almacenamiento automático en main()(como se discute en la respuesta de R. a continuación). Es realmente una lástima que esta pregunta esté etiquetada con C y C ++ (y con estilo de codificación, ¡no es un problema de estilo!).
Greg A. Woods
25

Otra diferencia: exites una función de biblioteca estándar, por lo que debe incluir encabezados y enlaces con la biblioteca estándar. Para ilustrar (en C ++), este es un programa válido:

int main() { return 0; }

pero para usar exitnecesitarás incluir:

#include <stdlib.h>
int main() { exit(EXIT_SUCCESS); }

Además, esto agrega una suposición adicional: que llamar exitdesde maintiene los mismos efectos secundarios que devolver cero. Como otros han señalado, esto depende de qué tipo de ejecutable esté construyendo (es decir, quién llama main). ¿Estás codificando una aplicación que usa el tiempo de ejecución C? ¿Un complemento maya? ¿Un servicio de Windows? ¿Un conductor? Cada caso requerirá investigación para ver si exites equivalente a return. En mi humilde opinión, usar exitcuando realmente te refieresreturn solo hace que el código sea más confuso. OTOH, si realmente quieres decirexit , entonces utilízalo .

jwfearn
fuente
16

Hay al menos una razón para preferir exit: si alguno de sus atexitcontroladores se refiere a datos de duración de almacenamiento automático main, o si usó setvbufo setbufasignó a una de las transmisiones estándar un búfer de duración de almacenamiento automático main, luego regresa de mainproduce comportamiento indefinido, pero la llamada exites válida.

Sin embargo, otro uso potencial (generalmente reservado para programas de juguetes) es salir de un programa con invocaciones recursivas de main.

R .. GitHub DEJA DE AYUDAR AL HIELO
fuente
1
@Seb no tiene nada de especial main(): es solo una función como cualquier otra. Por otro lado, dado que tiene una mención especial en el estándar, el estándar debe ser bastante cuidadoso acerca de cómo se define main()y las cosas cercanas y queridas. Sin embargo, al final, el estándar no requiere (y no debe ) que los compiladores hagan algo especial sobre el almacenamiento automático main(). Lea atentamente la Nota al pie # 11 debajo del párrafo al que hizo referencia en su comentario.
Greg A. Woods, el
1
@ GregA.Woods Interesante. Parece que hay algún texto normativo en contradicción con algún texto informativo. De acuerdo con las directivas ISO / IEC , la referencia normativa se considera "indispensable", mientras que el informativo se considera complementario ... Además, el uso de la palabra "voluntad" para transmitir un requisito no es válido; de acuerdo con el documento mencionado (Anexo H). En resumen, el texto informativo es ciertamente inválido.
autista
2
@Seb: la intención obviamente no es anular los requisitos sobre el comportamiento del almacenamiento automático y la nota al pie de página fue obviamente escrita para aclarar esto. Sí, hay una redacción imprecisa y deficiente en el estándar C. Cualquiera que lo haya leído lo sabe. También sabemos que el comité generalmente no soluciona problemas como este porque la intención ya es obvia.
R .. GitHub DEJA DE AYUDAR AL HIELO
1
@Seb: Este no es un debate o un caso judicial para demostrar que tienes razón. El objetivo debe ser obtener una comprensión clara de cuál es el lenguaje C real (según lo previsto e implementado), y expresarlo en respuestas que sean útiles para los lectores. El texto normativo es sutilmente incorrecto (contrario a la intención de lo que se suponía que debía expresar) de una manera que está esencialmente fijada por la nota al pie. Si no está satisfecho con esto, envíe un informe de defectos, pero no espere una respuesta. Así es como WG14 rueda ...
R .. GitHub DEJA DE AYUDAR A HIELO
3
@Seb: Parece creer que el lenguaje C se puede entender interpretando el texto en lenguaje natural del estándar como si fuera completamente riguroso. Esto simplemente no es posible. La especificación contiene errores, y WG14 no pierde el tiempo reescribiendo cosas cuando una simple nota al pie de página aclara que ya saben que cometieron un error, pero que el lector puede entenderlo.
R .. GitHub DEJA DE AYUDAR AL HIELO
5

Siempre uso returnporque el prototipo estándar para main()dice que devuelve un int.

Dicho esto, algunas versiones de los estándares dan mainun tratamiento especial y suponen que devuelve 0 si no hay una returndeclaración explícita . Dado el siguiente código:

int foo() {}
int main(int argc, char *argv[]) {}

G ++ solo genera una advertencia foo()e ignora el retorno faltante de main:

% g++ -Wall -c foo.cc
foo.cc: In function int foo()’:
foo.cc:1: warning: control reaches end of non-void function
Alnitak
fuente
No sé acerca de C, pero el estándar C ++ especifica que si no devuelve un valor en main, se supone que devuelve 0.
Jason Baker
Parece que C99 es lo mismo: faq.cprogramming.com/cgi-bin/…
Jason Baker
2
C99 y C ++ devuelven 0 si no hay una declaración de retorno, C90 no.
d0k
El hecho de que una función se declare que tiene un valor de retorno no significa que deba usarla returnpara finalizar su ejecución. Llamar exit()también es una forma válida, y a veces necesaria, de finalizar la ejecución de cualquier función. De hecho, como yo y otros hemos descrito en otra parte, llamar exit()incluso desde main()transmite una intención mucho más clara de salir de todo el proceso, preserva el almacenamiento automático hasta la salida del proceso y facilita el mantenimiento durante la futura refactorización del código. Para C usar returnin main()cuando la intención es terminar el proceso es, por lo tanto, una mala práctica.
Greg A. Woods
@ GregA.Woods esa es su opinión, ¡pero apenas merece un voto negativo! Lo que he escrito anteriormente es completamente consistente con el estándar , mientras que su argumento es solo semántico.
Alnitak
5

I ENÉRGICAMENTE segundo el comentario de R. acerca del uso de la salida () con el fin de evitar tener almacenamiento automático en main()recuperada antes de que el programa realmente extremos. Una return X;declaración en main()no es exactamente equivalente a una llamada a exit(X);, ya que el almacenamiento dinámico de main()desaparece cuando main()regresa, pero no desaparece si se realiza una llamada a exit().

Además, en C o en cualquier lenguaje similar a C, una returndeclaración sugiere al lector que la ejecución continuará en la función de llamada, y si bien esta continuación de la ejecución suele ser técnicamente cierta si cuenta la rutina de inicio de C que llamó a su main()función, no es exactamente lo que quieres decir cuando quieres terminar el proceso.

Después de todo, si usted desea terminar su programa desde cualquier otra función, excepto main()que debe llamar exit(). Hacerlo de manera consistente main()también hace que su código sea mucho más legible, y también hace que sea mucho más fácil para cualquiera volver a factorizar su código; es decir, el código copiado de main()alguna otra función no se comportará mal debido a returndeclaraciones accidentales que deberían haber sido exit()llamadas.

Entonces, combinando todos estos puntos, la conclusión es que es un mal hábito , al menos para C, usar una returndeclaración para finalizar el programa main().

Greg A. Woods
fuente
Puede encontrar interesante el 5.1.2.2.3p1 del estándar C ...
autista del
Vale la pena considerar cuidadosamente esta respuesta para los programas en C, como se indica contextualmente en la respuesta. Para usar con C ++, debe sopesarse cuidadosamente con todas las advertencias mencionadas anteriormente. Para C ++, sugeriría evitar exit()en general, pero lo uso si una throwo otras abort()alternativas no funcionan en un contexto específico. Pero especialmente evite exit()en main, y use return en main en su lugar como práctica típica.
Eljay
5

¿Exit () hace algo especial que 'return' no?

Con algunos compiladores para plataformas poco comunes, exit()podría traducir su argumento al valor de salida de su programa, mientras que un retorno de main()podría simplemente pasar el valor directamente al entorno host sin ninguna traducción.

El estándar requiere un comportamiento idéntico en estos casos (específicamente, dice que devolver algo que sea intcompatible main()debería ser equivalente a llamar exit()con ese valor). El problema es que diferentes sistemas operativos tienen diferentes convenciones para interpretar los valores de salida. En muchos sistemas (¡MUCHOS!), 0 significa éxito y cualquier otra cosa es un fracaso. Pero en, por ejemplo, VMS, los valores impares significan éxito y los pares significan fracaso. Si devolvió 0 desde main(), un usuario de VMS vería un mensaje desagradable sobre una infracción de acceso. En realidad no hubo una infracción de acceso, ese fue simplemente el mensaje estándar asociado con el código de falla 0.

Luego, ANSI apareció y fue bendecido EXIT_SUCCESSy EXIT_FAILUREcomo argumentos a los que podrías pasar exit(). La norma también dice que exit(0)debe comportarse de forma idéntica a exit(EXIT_SUCCESS), por lo que la mayoría de las implementaciones definen EXIT_SUCCESSa 0.

El estándar, por lo tanto, lo pone en un enlace en VMS, ya que no deja una forma estándar de devolver un código de falla que tiene el valor 0.

El compilador VAX / VMS C de principios de la década de 1990, por lo tanto, no interpretó el valor de retorno main(), simplemente devolvió cualquier valor al entorno host. Pero si lo usara exit(), haría lo que el estándar requería: traducir EXIT_SUCCESS(o 0) en un código de éxito y EXIT_FAILUREen un código de falla genérico. Para usarlo EXIT_SUCCESS, tenía que pasárselo exit(), no podía devolverlo main(). No sé si las versiones más modernas de ese compilador conservaron ese comportamiento.

Un programa C portátil solía verse así:

#include <stdio.h>
#include <stdlib.h>

int main() {
  printf("Hello, World!\n");
  exit(EXIT_SUCCESS);  /* to get good return value to OS */
  /*NOTREACHED*/ /* to silence lint warning */
  return 0;  /* to silence compiler warning */
}

Aparte: si recuerdo correctamente, la convención VMS para valores de salida tiene más matices que impar / par. En realidad, usa algo como los tres bits bajos para codificar un nivel de gravedad. En términos generales, sin embargo, los niveles de gravedad impares indicaron éxito o información variada y los pares indicaron errores.

Adrian McCarthy
fuente
Algunos compiladores anteriores a ANSI anteriores podrían haber tratado el valor returnedde manera maindiferente del valor pasado a exit, pero el estándar dice específicamente: "Si el tipo de retorno de la mainfunción es un tipo compatible con int, un retorno de la llamada inicial a la mainfunción es equivalente a llamar a la exitfunción con el valor devuelto por la mainfunción como argumento ". Eso es C11; C89 / C90 tenía casi la misma redacción.
Keith Thompson el
En efecto. Sin embargo, algunos compiladores de la era ANSI no lo hicieron bien y requirieron el uso explícito de exit para obtener el valor de retorno correcto devuelto al entorno host. Dado que el estándar (incluso entonces) requiere que 0 sea tratado de la misma manera EXIT_SUCCESS, no había forma de devolver un estado de falla específico de la plataforma con el valor 0, por lo que algunos de los compiladores de la era trataron el retorno desde el principal Y exit()diferente.
Adrian McCarthy
¿Tienes una cita para eso? Otra cuestión es si algún compilador actual tiene ese error en particular. Su respuesta es para redactado en tiempo presente.
Keith Thompson
Esa es una crítica justa. He cambiado la redacción para limitar el alcance al caso específico que conozco.
Adrian McCarthy
0

En C regresar de maines exactamente lo mismo que llamar exitcon el mismo valor.

La sección 5.1.2.2.3 de los estados estándar de C :

Si el tipo de retorno de la función principal es un tipo compatible con int, un retorno de la llamada inicial a la función principal es equivalente a llamar a la función de salida con el valor devuelto por la función principal como argumento ; 11) alcanzar el} que finaliza la función principal devuelve un valor de 0. Si el tipo de retorno no es compatible con int, el estado de terminación devuelto al entorno host no está especificado.

Las reglas para C ++ son un poco diferentes como se menciona en otras respuestas.

dbush
fuente
-1

En realidad, existe una diferencia entre exit(0)y return(0)en main- cuando su mainfunción se llama varias veces.

El siguiente programa

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc == 0)
    return(0);
  printf("%d", main(argc - 1, argv));
}

Correr como

./program 0 0 0 0

Resultará en la siguiente salida:

00000

Sin embargo este:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc == 0)
    exit(0);
  printf("%d", main(argc - 1, argv));
}

No imprimirá nada independientemente de los argumentos.

Si está seguro de que nadie lo llamará mainexplícitamente, técnicamente no es una gran diferencia en general, pero mantener un código más claro exitse vería mucho mejor. Si por alguna razón desea llamar main, debe ajustarlo a sus necesidades.

Hablando de C.

radrow
fuente