declaração de retorno vs exit () em main ()

197

Devo usar exit()ou apenas returndeclarações main()? Pessoalmente, sou a favor das returndeclarações porque sinto que é como ler qualquer outra função e o controle de fluxo quando estou lendo o código é suave (na minha opinião). E mesmo que eu queira refatorar a main()função, ter returnparece ser uma escolha melhor que exit().

Faz exit()algo especial que returnnão faz?

Srikanth
fonte

Respostas:

277

Na verdade, não é uma diferença, mas é sutil. Tem mais implicações para C ++, mas as diferenças são importantes.

Quando eu chamar returnem main(), destruidores serão chamados para os meus objetos escopo localmente. Se eu ligar exit(), nenhum destruidor será chamado para meus objetos com escopo local! Releia isso. exit() não retorna . Isso significa que, assim que eu chamo, não há "backsies". Quaisquer objetos que você criou nessa função não serão destruídos. Frequentemente, isso não tem implicações, mas às vezes ocorre, como o fechamento de arquivos (certamente você deseja que todos os seus dados sejam descarregados para o disco?).

Observe que os staticobjetos serão limpos mesmo se você ligar exit(). Por fim, observe que se você usarabort() , nenhum objeto será destruído. Ou seja, nenhum objeto global, nenhum objeto estático e nenhum objeto local terão seus destruidores chamados.

Prossiga com cuidado ao favorecer a saída em vez do retorno.

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

Memoria livre
fonte
1
abort () sai com condição de erro (código de saída diferente de zero) e pode ser até um núcleo. Se você precisar sair sem chamar destruidores estáticos, use _exit.
7
@ Mike: há uma diferença entre os buffers de arquivos da biblioteca C e os objetos de fluxo de arquivos C ++. exit () - fazendo parte da biblioteca C - foi projetado para coordenar e liberar a primeira, mas pode ignorar a segunda: mesmo o conteúdo padrão do fluxo de fluxo C ++ não é liberado para o disco (tente - eu fiz, falhei com o Linux / GCC) e, obviamente, tipos definidos pelo usuário que tenham E / S em buffer também não podem ser liberados.
Tony Delroy
9
Nota: A declaração: nenhum destruidor será chamado para meus objetos com escopo local! não é mais verdadeiro para C ++ 11: - Os objetos associados ao thread atual com duração de armazenamento de threads são destruídos (apenas C ++ 11). cplusplus.com/reference/cstdlib/exit
Ilendir
7
Isso significa thread_localque os destruidores de objetos serão chamados. Destruidores para outros objetos locais ainda não são chamados. ideone.com/Y6Dh3f
HolyBlackCat
3
BTW, e apenas para ser pedante e porque essa resposta ainda pode ser confusa para os leitores que usam C: Para C, a questão sobre o exit()fechamento de arquivos de forma limpa está realmente errada. O único momento em que os dados podem não ser liberados é no caso oposto: ou seja, se um usa returnfrom main()e um chamou setbuf()ou setvbuf()com um buffer declarado como armazenamento automático em main()(como discutido na resposta de R. abaixo). É uma pena que esta questão seja marcada com C e C ++ (e com estilo de codificação - não é um problema de estilo!).
Greg A. Woods
25

Outra diferença: exité uma função da Biblioteca Padrão, portanto é necessário incluir cabeçalhos e vincular-se à biblioteca padrão. Para ilustrar (em C ++), este é um programa válido:

int main() { return 0; }

mas para usar, exitvocê precisará incluir:

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

Além disso, isso adiciona uma suposição adicional: a chamada exitde maintem os mesmos efeitos colaterais do retorno zero. Como outros já apontaram, isso depende do tipo de executável que você está construindo (ou seja, quem está chamando main). Você está codificando um aplicativo que usa o C-runtime? Um plugin do Maya? Um serviço do Windows? Um motorista? Cada caso exigirá pesquisa para ver se exité equivalente a return. IMHO usando exitquando você realmente quer dizer return apenas torna o código mais confuso. OTOH, se você realmente quer dizer exit , então, por todos os meios, use-o.

jwfearn
fonte
16

Há pelo menos uma razão para preferir exit: Se qualquer um dos seus atexitmanipuladores referem-se a dados de armazenamento de duração automática no main, ou se você usou setvbufou setbufatribuir a um dos padrões córregos tampão em um de armazenamento de duração automática main, em seguida, retornar de mainproduz comportamento indefinido, mas a chamada exité válida.

Outro uso potencial (geralmente reservado para programas de brinquedo, no entanto) é sair de um programa com invocações recursivas de main.

R .. GitHub PARE DE AJUDAR O GELO
fonte
1
@ Seeb não há nada de especial main()- é apenas uma função como outra qualquer. Por outro lado, uma vez que tem menção especial no padrão, o padrão deve ser bastante cuidadoso sobre como define main()e as coisas próximas e queridas. No entanto, no final, embora o padrão não exija (e não deva ) exigir que os compiladores façam algo de especial no armazenamento automático main(). Leia a nota de rodapé nº 11 abaixo do parágrafo que você referenciou em seu comentário.
Greg A. Woods,
1
@ GregA.Woods Interessante. Parece que há algum texto normativo em contradição com algum texto informativo. De acordo com as diretrizes da ISO / IEC , a referência normativa é considerada "indispensável", sendo que o informativo é considerado apenas suplementar ... Além disso, o uso da palavra "vontade" para transmitir um requisito é inválido; de acordo com o documento mencionado (Anexo H). Em resumo, o texto informativo é certamente inválido.
Autistic
2
@ SEB: Obviamente, a intenção é não substituir os requisitos sobre o comportamento do armazenamento automático e a nota de rodapé foi obviamente escrita para esclarecer isso. Sim, há palavras imprecisas e ruins no padrão C. Quem leu sabe disso. Também sabemos que o comitê geralmente não resolve problemas como esse, porque a intenção já é óbvia.
R .. GitHub Pare de ajudar o gelo
1
@ Seb: Este não é um debate ou processo judicial para provar que você está certo. O objetivo deve ser obter uma compreensão clara do que é a linguagem C real (como pretendida e implementada) e expressar isso em respostas úteis para os leitores. O texto normativo está sutilmente errado (ao contrário da intenção do que deveria expressar) de uma maneira que é essencialmente fixada pela nota de rodapé. Se você não estiver satisfeito com isso, envie um relatório de defeitos, mas não espere uma resposta. É assim que o WG14 rola ...
R .. GitHub PARE DE AJUDAR O GELO
3
@ Seeb: Você parece acreditar que a linguagem C pode ser entendida interpretando o texto em linguagem natural do padrão como se fosse completamente rigoroso. Isso simplesmente não é possível. A especificação contém erros, e o WG14 não perde tempo reescrevendo coisas quando uma nota de rodapé simples esclarece que eles já sabem que cometeram um erro, mas que o leitor pode entendê-lo.
R .. GitHub Pare de ajudar o gelo
5

Eu sempre uso returnporque o protótipo padrão para main()diz que ele retorna um int.

Dito isto, algumas versões dos padrões dão maintratamento especial e assumem que retornará 0 se não houver uma returndeclaração explícita . Dado o seguinte código:

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

O G ++ gera apenas um aviso foo()e ignora o retorno ausente 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
fonte
Eu não sei sobre C, mas especifica padrão do C ++ que se você não retornar um valor no principal, é assumido para retornar 0.
Jason Baker
Parece que C99 é o mesmo: faq.cprogramming.com/cgi-bin/…
Jason Baker
2
C99 e C ++ retornam 0 se não houver declaração de retorno, C90 não.
djk
Só porque uma função é declarada como tendo um valor de retorno não significa que você deve usar returnpara finalizar sua execução. A chamada exit()também é uma maneira válida e, às vezes, necessária para finalizar a execução de qualquer função. De fato, como eu e outros descrevemos em outro lugar, chamar exit()mesmo main()transmite uma intenção muito mais clara de sair de todo o processo, preserva o armazenamento automático até a saída do processo e facilita a manutenção durante a refatoração futura do código. Para C, utilizando returnem main()quando a intenção é a de terminar o processo é, assim, sem dúvida, uma má prática.
Greg A. Woods,
@ GregA.Woods é a sua opinião, mas dificilmente merece um voto negativo! O que escrevi acima é completamente consistente com o padrão , enquanto seu argumento é apenas semântica.
Alnitak
5

Eu enfaticamente enfatizo o comentário de R. sobre o uso de exit () para evitar a main()recuperação automática do armazenamento antes que o programa realmente termine. Uma return X;declaração in main()não é precisamente equivalente a uma chamada para exit(X);, uma vez que o armazenamento dinâmico de main()desaparece quando main()retorna, mas não desaparece se uma chamada exit()for feita.

Além disso, em C ou em qualquer linguagem semelhante a C, uma returndeclaração sugere fortemente ao leitor que a execução continuará na função de chamada e, embora essa continuação de execução seja tecnicamente verdadeira, se você contar a rotina de inicialização C que chamou sua main()função, não é exatamente o que você quer dizer quando pretende terminar o processo.

Afinal, se você deseja finalizar seu programa a partir de qualquer outra função, exceto que main()você deve chamarexit() . Fazer isso de forma consistente main()também torna seu código muito mais legível e também torna muito mais fácil para alguém re-fatorar seu código; ou seja, o código copiado main()para outra função não se comportará mal por causa de returndeclarações acidentais que deveriam ter sido exit()chamadas.

Portanto, combinando todos esses pontos, a conclusão é que é um mau hábito , pelo menos para C, usar uma returndeclaração para encerrar o programa emmain() .

Greg A. Woods
fonte
Você pode achar 5.1.2.2.3p1 do padrão C interessante ...
autista
Vale a pena considerar esta resposta para os programas C, conforme indicado contextualmente na resposta. Para uso com C ++, ele precisa ser cuidadosamente ponderado em relação a todas as advertências mencionadas anteriormente. Para C ++, eu sugeriria evitar exit()em geral, mas use-o se a throwou abort()alternativas não funcionarem em um contexto específico. Mas especialmente evite exit()em main e use o retorno em main como prática típica.
quer
5

Exit () faz algo especial que 'return' não faz?

Com alguns compiladores para plataformas incomuns, exit()pode converter seu argumento no valor de saída do seu programa enquanto um retorno demain() apenas pode passar o valor diretamente para o ambiente host sem nenhuma tradução.

O padrão requer um comportamento idêntico nesses casos (especificamente, ele diz que retornar algo que é intcompatível main()deve ser equivalente a chamar exit()com esse valor). O problema é que sistemas operacionais diferentes têm convenções diferentes para interpretar os valores de saída. Em muitos sistemas (MUITOS!), 0 significa sucesso e qualquer outra coisa é um fracasso. Mas, digamos, VMS, valores ímpares significam sucesso e pares significam falha. Se você retornou 0 demain() , um usuário do VMS receberia uma mensagem desagradável sobre uma violação de acesso. Na verdade, não houve uma violação de acesso - essa era simplesmente a mensagem padrão associada ao código de falha 0.

Então o ANSI apareceu e abençoou EXIT_SUCCESSe EXIT_FAILUREcomo argumentos você poderia passar exit(). A norma também diz que exit(0)deve comportar-se de forma idêntica para exit(EXIT_SUCCESS), assim que a maioria das implementações definir EXIT_SUCCESSa 0.

O padrão, portanto, coloca você em um vínculo com o VMS, pois não deixa uma maneira padrão de retornar um código de falha que possua o valor 0.

O compilador VAX / VMS C da era do início dos anos 90, portanto, não interpretou o valor de retorno main(), simplesmente retornou qualquer valor ao ambiente host. Mas se você o utilizasse exit(), faria o que o padrão exigia: converter EXIT_SUCCESS(ou 0) em um código de sucesso e EXIT_FAILUREem um código de falha genérico. Para usar EXIT_SUCCESS, você tinha que passá-lo para exit(), você não poderia devolvê-lo main(). Não sei se versões mais modernas desse compilador preservaram esse comportamento.

Um programa C portátil costumava ficar assim:

#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 */
}

Além: Se bem me lembro, a convenção VMS para valores de saída é mais diferenciada do que ímpar / par. Na verdade, ele usa algo como os três bits baixos para codificar um nível de gravidade. De um modo geral, no entanto, os níveis ímpares de gravidade indicavam sucesso ou informações diversas e os pares indicavam erros.

Adrian McCarthy
fonte
Alguns antigos compiladores pré-ANSI poderia ter tratado o valor returnedde mainforma diferente do valor passado para exit- mas o padrão diz especificamente: "Se o tipo de retorno da mainfunção é um tipo compatível com int, um retorno da chamada inicial para a mainfunção é equivalente a chamar a exitfunção com o valor retornado pela mainfunção como argumento ". Isso é C11; C89 / C90 tinha quase a mesma redação.
Keith Thompson
De fato. No entanto, alguns compiladores da era ANSI não acertaram e exigiram o uso explícito de exit para obter o valor de retorno correto retornado ao ambiente host. Como o padrão (mesmo assim) exige que 0 seja tratado da mesma maneira que EXIT_SUCCESS, não havia como retornar um status de falha específico da plataforma com o valor 0, e pode ser por isso que alguns dos compiladores da época trataram o retorno do principal e exit()diferente.
Adrian McCarthy
Você tem uma citação para isso? Uma questão separada é se algum compilador atual possui esse bug específico. Sua resposta é formulada no tempo presente.
Keith Thompson
Essa é uma crítica justa. Alterei a redação para limitar o escopo ao caso específico que conheço.
Adrian McCarthy
0

Em C, retornar de mainé exatamente o mesmo que chamar exitcom o mesmo valor.

A seção 5.1.2.2.3 da norma C declara:

Se o tipo de retorno da função principal for compatível com int, um retorno da chamada inicial para a função principal será equivalente a chamar a função de saída com o valor retornado pela função principal como argumento ; 11) alcançar o} que finaliza a função principal retorna um valor 0. Se o tipo de retorno não for compatível com int, o status de finalização retornado ao ambiente host não é especificado.

As regras para C ++ são um pouco diferentes, conforme mencionado em outras respostas.

dbush
fonte
-1

Há realmente uma diferença entre exit(0)e return(0)em main- quando a sua mainfunção é chamada várias vezes.

O seguinte 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á na seguinte saída:

00000

No entanto, este:

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

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

Não imprimirá nada, independentemente dos argumentos.

Se você tem certeza de que ninguém nunca ligará para você mainexplicitamente, não é tecnicamente uma grande diferença em geral, mas manter um código mais claro exitseria muito melhor. Se você, por algum motivo, quiser ligar main- você deve ajustá-lo às suas necessidades.

Falando sobre C.

radrow
fonte