Como finalizar o código C ++


267

Gostaria que meu código C ++ parasse de executar se uma determinada condição fosse atendida, mas não sei como fazer isso. Portanto, a qualquer momento, se uma ifafirmação for verdadeira, encerre o código da seguinte maneira:

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

98
Use lógica adequada. Em main()retorno de uso, em funções, use um valor de retorno adequado ou lance uma exceção adequada. Não use exit()!
kay - SE is evil

6
@ JonathanLeffler: Quando compilado com o NDEBUGdefinido, assertprovavelmente se tornará um não operacional, então você não deve confiar nele para mais do que detectar bugs.
MvG 15/05

13
@ jamesqf: a returnfrom main()é perfeitamente lógico. Ele define o código de saída relatado pelo programa para o sistema operacional, o que pode ser útil em muitos casos (se o seu programa puder ser usado como parte de um processo maior, por exemplo).
AAT

11
Curiosamente, este Q é uma duplicata deste de 5 anos atrás, cuja resposta aceita é bem diferente: stackoverflow.com/questions/1116493/how-to-quit-ac-program Acho que a comunidade levou cinco anos para aprender isso cegamente chamar std :: exit pode ser ruim?
Brandin 15/05

5
Perguntas Por que o uso é exit()considerado ruim? - SO 25141737 e Como encerrar um programa C ++? - SO 1116493 agora estão fechados como duplicados deste. O primeiro deles tem uma referência a 'Crash-Software' que pode valer uma olhada, apenas para estimular o seu pensamento sobre como escrever software robusto.
Jonathan Leffler

Respostas:


431

Existem várias maneiras, mas primeiro você precisa entender por que a limpeza de objetos é importante e, portanto, o motivo std::exité marginalizado entre os programadores de C ++.

RAII e desbobinamento de pilha

O C ++ faz uso de um idioma chamado RAII , que em termos simples significa que os objetos devem executar a inicialização no construtor e a limpeza no destruidor. Por exemplo, a std::ofstreamclasse [pode] abrir o arquivo durante o construtor, o usuário realiza operações de saída e, finalmente, no final de seu ciclo de vida, geralmente determinado por seu escopo, é chamado o destruidor que essencialmente fecha o arquivo e libera qualquer conteúdo escrito no disco.

O que acontece se você não chegar ao destruidor para liberar e fechar o arquivo? Quem sabe! Mas, possivelmente, ele não gravará todos os dados que deveria gravar no arquivo.

Por exemplo, 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);
}

O que acontece em cada possibilidade é:

  • Possibilidade 1: Return essencialmente deixa o escopo da função atual, para que ele saiba sobre o final do ciclo de vida os, chamando seu destruidor e fazendo a limpeza adequada, fechando e liberando o arquivo para o disco.
  • Possibilidade 2: lançar uma exceção também cuida do ciclo de vida dos objetos no escopo atual, fazendo assim uma limpeza adequada ...
  • Possibilidade 3: Aqui o desenrolar da pilha entra em ação! Mesmo que a exceção seja lançada inner_mad, o desbobinador passará pela pilha made mainrealizará a limpeza adequada, todos os objetos serão destruídos corretamente, incluindo ptre os.
  • Possibilidade 4: Bem, aqui? exité uma função C e não está ciente nem é compatível com os idiomas C ++. Ele não executa a limpeza em seus objetos, inclusive osno mesmo escopo. Portanto, seu arquivo não será fechado corretamente e, por esse motivo, o conteúdo poderá nunca ser gravado nele!
  • Outras possibilidades: deixará o escopo principal apenas, executando um implícito return 0e, portanto, tendo o mesmo efeito que a possibilidade 1, ou seja, limpeza adequada.

Mas não tenha tanta certeza do que acabei de lhe contar (principalmente as possibilidades 2 e 3); continue lendo e descobriremos como executar uma limpeza adequada baseada em exceções.

Possíveis maneiras de terminar

Retorno do main!

Você deve fazer isso sempre que possível; sempre prefere retornar do seu programa retornando um status de saída adequado da main.

O chamador do seu programa e, possivelmente, o sistema operacional, pode querer saber se o que seu programa deveria fazer foi feito com êxito ou não. Por esse mesmo motivo, você deve retornar zero ou EXIT_SUCCESSsinalizar que o programa foi finalizado com êxito e EXIT_FAILUREsinalizar que o programa foi finalizado sem êxito, qualquer outra forma de valor de retorno é definida pela implementação ( §18.5 / 8 ).

No entanto, você pode estar muito envolvido na pilha de chamadas e retornar tudo isso pode ser doloroso ...

[Não] lance uma exceção

Lançar uma exceção executará a limpeza adequada do objeto usando o desenrolamento da pilha, chamando o destruidor de cada objeto em qualquer escopo anterior.

Mas aqui está o problema ! É definido pela implementação se o desenrolamento da pilha é executado quando uma exceção lançada não é tratada (pela cláusula catch (...)) ou mesmo se você tem uma noexceptfunção no meio da pilha de chamadas. Isto é afirmado em §15.5.1 [exceto.terminar] :

  1. Em algumas situações, o tratamento de exceções deve ser abandonado para técnicas de tratamento de erros menos sutis. [Nota: Essas situações são:

    [...]

    - quando o mecanismo de tratamento de exceções não puder encontrar um manipulador para uma exceção lançada (15.3), ou quando a procura de um manipulador (15.3) encontrar o bloco mais externo de uma função com uma noexceptespecificação que não permita a exceção (15.4), ou [...]

    [...]

  2. Nesses casos, std :: terminate () é chamado (18.8.3). Na situação em que nenhum manipulador correspondente é encontrado, é definido como implementação se a pilha é ou não desenrolada antes que std :: terminate () seja chamado [...]

Então nós temos que pegá-lo!

Lance uma exceção e pegue-a na principal!

Como as exceções não capturadas podem não executar o desenrolamento da pilha (e consequentemente não executam a limpeza adequada) , devemos capturar a exceção no main e retornar um status de saída ( EXIT_SUCCESSou EXIT_FAILURE).

Portanto, uma configuração possivelmente boa seria:

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;
    }
    /* ... */
}

[Não] std :: exit

Isso não realiza nenhum tipo de desenrolamento da pilha, e nenhum objeto ativo na pilha chama seu respectivo destruidor para executar a limpeza.

Isso é imposto em §3.6.1 / 4 [basic.start.init] :

Encerrar o programa sem sair do bloco atual (por exemplo, chamando a função std :: exit (int) (18.5)) não destrói nenhum objeto com duração automática de armazenamento (12.4) . Se std :: exit for chamado para encerrar um programa durante a destruição de um objeto com duração de armazenamento estático ou de encadeamento, o programa terá um comportamento indefinido.

Pense nisso agora, por que você faria uma coisa dessas? Quantos objetos você danificou dolorosamente?

Outras alternativas [tão ruins]

Existem outras maneiras de encerrar um programa (além de travar) , mas elas não são recomendadas. Apenas para fins de esclarecimento, eles serão apresentados aqui. Observe como o término normal do programa não significa o desenrolamento da pilha, mas um estado aceitável para o sistema operacional.

  • std::_Exit causa um encerramento normal do programa, e é isso.
  • std::quick_exitcausa uma finalização normal do programa e chama os std::at_quick_exitmanipuladores, nenhuma outra limpeza é executada.
  • std::exitcausa um encerramento normal do programa e chama os std::atexitmanipuladores. Outros tipos de limpeza são executados, como chamar destruidores de objetos estáticos.
  • std::abortcausar um encerramento anormal do programa, nenhuma limpeza será realizada. Isso deve ser chamado se o programa for finalizado de uma maneira realmente inesperada. Ele não fará nada além de sinalizar o SO sobre o término anormal. Alguns sistemas executam um dump principal nesse caso.
  • std::terminatechama o std::terminate_handlerque chama std::abortpor padrão.

25
Isso é bastante informativo: notavelmente eu nunca percebi que lançar uma exceção que não é tratada em lugar nenhum (por exemplo: alguns newlances std::bad_alloce seu programa esqueceu de capturar essa exceção em qualquer lugar) não desenrolará adequadamente a pilha antes de terminar. Isso parece bobo para mim: ela teria sido fácil, essencialmente envolver a chamada para mainum trivial try{- }catch(...){}bloco que garantiria pilha desenrolamento é feito corretamente, em tais casos, sem nenhum custo (por que eu quero dizer: programas não usar este não pagará pena). Existe alguma razão específica para isso não ser feito?
Marc Van Leeuwen

12
@MarcvanLeeuwen uma razão possível é a depuração: você deseja entrar no depurador assim que uma exceção não tratada for lançada. Desenrolar a pilha e fazer a limpeza apagaria o contexto do que causou a falha que você deseja depurar. Se nenhum depurador estiver presente, seria melhor despejar o núcleo para que a análise post-mortem possa ser feita.
Hugh Allen #

11
Às vezes, meu navegador falha (culpo o flash) e consome vários gigabytes de RAM e meu sistema operacional despeja páginas no disco rígido, tornando tudo lento. Quando fecho o navegador, ele desenrola adequadamente a pilha, o que significa que todos esses gigabytes de RAM são lidos no disco rígido e copiados para a memória apenas para serem liberados, o que leva mais ou menos um minuto. Eu gostaria que eles tivessem usado, std::abortpara que o sistema operacional possa liberar toda a memória, soquetes e descritores de arquivos sem trocar por um minuto.
Nwp 15/05

2
@ NWP, eu entendo o sentimento. Mas matar arquivos corrompidos instantaneamente maio, não salvar os meus guias mais recente, etc :)
Paul Draper

2
@PaulDraper Eu certamente não consideraria tolerável se um navegador não pudesse restaurar as guias que eu abri antes de uma perda de energia. É claro que se eu tivesse acabado de abrir uma guia e ainda não tivesse tido tempo de salvá-la, ela estaria perdida. Mas fora isso, digo que não há desculpa para perdê-lo.
Kasperd 17/05

61

Como Martin York mencionou, a saída não realiza a limpeza necessária, como o retorno.

É sempre melhor usar o retorno no local de saída. Caso você não esteja no main, onde quer que você saia do programa, volte ao main primeiro.

Considere o exemplo abaixo. Com o programa a seguir, um arquivo será criado com o conteúdo mencionado. Mas se o retorno for comentado e a saída não comentada (0), o compilador não garante que o arquivo terá o texto necessário.

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

Não é só isso, ter vários pontos de saída em um programa tornará a depuração mais difícil. Use exit somente quando isso puder ser justificado.


2
O que você recomenda para atingir esse comportamento em um programa um pouco maior? Como você sempre retorna ao main de forma limpa, se em algum lugar mais profundo do código é acionada uma condição de erro que deve sair do programa?
Janusz

5
@Janusz, Nesse caso, você pode usar / lançar exceções, se não retornar um valor predefinido, ou seja, ter um valor de retorno da função, como por exemplo, retornar 0 quando for bem-sucedido, 1 no caso de falha, mas continuar a execução -1 em caso de falha e saia do programa. Com base no valor de retorno da função, se houver uma falha, basta retornar do principal após executar mais atividades de limpeza. Finalmente, use exit criteriosamente, não pretendo evitá-lo.
Narendra N

1
@NarendraN, "limpeza necessária" é vaga - o sistema operacional cuidará (Windows / Linux) de que os identificadores de memória e arquivo sejam liberados corretamente. Quanto à saída do arquivo "ausente": Se você insistir que isso pode ser um problema real, consulte stackoverflow.com/questions/14105650/how-does-stdflush-work Se você tiver uma condição de erro, o registro adequado informará que o seu programa atingiu um estado indefinido e você pode definir um ponto de interrupção antes do seu ponto de registro. Como isso torna a depuração mais difícil?
Markus

2
Observe que esta resposta foi mesclada da pergunta Como encerrar um programa C ++? - SO 1116493 . Esta resposta foi escrita aproximadamente 6 anos antes desta pergunta ser feita.
31816 Jonathan Leffler

para C ++ moderno utilizar return (EXIT_SUCCESS);vezreturn(0)
Jonas Stein

39

Chame a std::exitfunção   


2
Destruidores de quais objetos são chamados quando você chama essa função?
Rob Kennedy

37
exit () não retorna. Portanto, nenhum desenrolamento de pilha pode ocorrer. Nem mesmo objetos globais são destruídos. Mas as funções registradas com atexit () serão chamadas.
Martin Iorque

16
Por favor, não ligue exit()quando o código da sua biblioteca estiver sendo executado dentro do meu processo host - o último sairá no meio do nada.
Sharptooth

5
Observe que esta resposta foi mesclada da pergunta Como encerrar um programa C ++? - SO 1116493 . Esta resposta foi escrita aproximadamente 6 anos antes desta pergunta ser feita.
31816 Jonathan Leffler

23

As pessoas estão dizendo "saída de chamada (código de retorno)", mas esta é uma forma incorreta. Em programas pequenos, tudo bem, mas há vários problemas com isso:

  1. Você terá vários pontos de saída do programa
  2. Torna o código mais complicado (como usar goto)
  3. Ele não pode liberar memória alocada em tempo de execução

Realmente, a única vez que você deve sair do problema é com esta linha em main.cpp:

return 0;

Se você estiver usando exit () para lidar com erros, deve aprender sobre exceções (e exceções de aninhamento), como um método muito mais elegante e seguro.


9
Em um ambiente multiencadeado, uma exceção lançada em um encadeamento diferente não será manipulada por main () - é necessária alguma comunicação manual de encadeamento cruzado antes que o encadeamento subordinado expire.
21119 Steve Gilham

3
1. e 2. dependem do programador, com o registro adequado, isso não é um problema, pois a execução pára para sempre. Quanto a 3: isso está totalmente errado, o sistema operacional liberará a memória - talvez excluindo dispositivos incorporados / em tempo real, mas se você estiver fazendo isso, provavelmente já sabe tudo.
Markus

1
Observe que esta resposta foi mesclada da pergunta Como encerrar um programa C ++? - SO 1116493 . Esta resposta foi escrita aproximadamente 6 anos antes desta pergunta ser feita.
31816 Jonathan Leffler

1
Se ocorreu um erro, no entanto, você não deve retornar 0. Você deve retornar 1 (ou possivelmente algum outro valor, mas 1 é sempre uma opção segura).
Kef Schecter

14

return 0;coloque isso onde quiser int main()e o programa será fechado imediatamente.


the-nightman, @ evan-carslake e todos os outros, também acrescentaria que esta pergunta SO 36707 fornece uma discussão sobre se uma declaração de retorno deve ocorrer em qualquer lugar da rotina. Esta é uma solução; no entanto, dependendo da situação específica, não diria que esta é a melhor solução.
Localhost

1
O OP não disse nada sobre isso, então acho que é um pouco precipitado supor que o código deveria estar dentro da função main.
Marc van Leeuwen 15/05

@localhost Uma declaração de retorno é completamente opcional em todas as situações, sob quaisquer condições. No entanto, int main()é diferente. O programa usa int main()para começar e terminar. Não há outra maneira de fazer isso. O programa será executado até você retornar verdadeiro ou falso. Na maioria das vezes, você retornará 1 ou true, para indicar que o programa foi fechado corretamente (por exemplo: pode usar false se você não conseguir liberar memória ou qualquer outro motivo.) Deixar o programa ser executado é sempre uma má idéia, por exemplo:int main() { int x = 2; int foo = x*5; std::cout << "blah"; }
Evan Carslake

@EvanCarslake Entendido, se você notou um comentário que eu postei em outro lugar para esta pergunta, estou familiarizado com isso int main; no entanto, nem sempre é uma boa ideia ter várias instruções de retorno em uma rotina principal. Uma ressalva em potencial é melhorar a legibilidade do código; no entanto, usar algo como um sinalizador booleano que altera o estado para impedir que determinadas seções de código sejam executadas nesse estado do aplicativo. Pessoalmente, acho que uma declaração de retorno comum torna a maioria dos aplicativos mais legíveis. Por fim, perguntas sobre limpeza de memória e fechamento de objetos de E / S corretamente antes da saída.
Localhost

2
@EvanCarslake você não voltar truea partir mainpara indicar uma terminação correta. Você deve retornar zero ou EXIT_SUCCESS(ou, se desejar false, que seria implicitamente convertido em zero) para indicar finalização normal. Para indicar falha, você pode retornar EXIT_FAILURE. Qualquer outro significado de código é definido pela implementação (em sistemas POSIX, isso significa código de erro real).
Ruslan

11

O programa será encerrado quando o fluxo de execução atingir o final da função principal.

Para finalizá-lo antes disso, você pode usar a função exit (int status), em que status é um valor retornado para o que iniciou o programa. 0 normalmente indica um estado sem erro


2
Observe que esta resposta foi mesclada da pergunta Como encerrar um programa C ++? - SO 1116493 . Esta resposta foi escrita aproximadamente 6 anos antes desta pergunta ser feita.
31416 Jonathan Leffler

11

Retorne um valor seu mainou use a exitfunção Ambos assumem um int. Realmente não importa qual valor você devolve, a menos que tenha um processo externo observando o valor de retorno.


3
Observe que esta resposta foi mesclada da pergunta Como encerrar um programa C ++? - SO 1116493 . Esta resposta foi escrita aproximadamente 6 anos antes desta pergunta ser feita.
31416 Jonathan Leffler


9

Geralmente você usaria o exit()método com um status de saída apropriado .

Zero significaria uma corrida bem-sucedida. Um status diferente de zero indica que ocorreu algum tipo de problema. Esse código de saída é usado pelos processos pai (por exemplo, scripts de shell) para determinar se um processo foi executado com êxito.


1
usando exit pode significar que você tem um problema de design. Se um programa funcionar corretamente, ele deve terminar quando principal return 0;. Eu suponho que exit()é como assert(false);e, portanto, deve ser usado apenas no desenvolvimento para detectar problemas precocemente.
CoffeDeveloper

2
Observe que esta resposta foi mesclada da pergunta Como encerrar um programa C ++? - SO 1116493 . Esta resposta foi escrita aproximadamente 6 anos antes desta pergunta ser feita.
31416 Jonathan Leffler

7

Além de chamar exit (error_code) - que chama manipuladores atexit, mas não destruidores de RAII etc. -, cada vez mais eu uso exceções.

Cada vez mais meu programa principal se parece

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);
    }
}

onde primary_main em onde todas as coisas originalmente foram colocadas - ou seja, o main original é renomeado como secundário_main e o stub main acima é adicionado. Isso é apenas um detalhe, para que não haja muito código entre a bandeja e a captura principal.

Se desejar, pegue outros tipos de exceção.
Eu gosto bastante de capturar tipos de erro de string, como std :: string ou char *, e imprimir aqueles no manipulador de captura principal.

Usar exceções como essa pelo menos permite que os destruidores de RAII sejam chamados, para que eles possam fazer a limpeza. O que pode ser agradável e útil.

No geral, o tratamento de erros em C - saída e sinais - e o tratamento de erros em C ++ - exceções de tentar / capturar / lançar - são executados juntos na melhor das hipóteses.

Então, onde você detecta um erro

throw "error message"

ou algum tipo de exceção mais específico.


A propósito: estou ciente de que o uso de um tipo de dados como string, que pode envolver alocação dinâmica de memória, não é uma boa idéia dentro ou ao redor de um manipulador de exceções para exceções que possam estar relacionadas à falta de memória. As constantes de string no estilo C não são um problema.
Krazy Glew

1
Não faz sentido chamar exitseu programa. Desde que você está main, você pode apenas return exitCode;.
Ruslan

-1

Se a sua declaração if estiver em loop, você pode usar

 break; 

Se você deseja escapar de algum código e continuar em loop, use:

continuar;

Se sua instrução if não estiver em Loop, você pode usar:

 return 0;

Or 




  exit();

-2

A exit()função Cara ... é definida em stdlib.h

Então você precisa adicionar um pré-processador.

Coloque include stdlib.hna seção de cabeçalho

Em seguida, use exit();onde quiser, mas lembre-se de colocar um número intermediário entre parênteses de saída.

por exemplo:

exit(0);

-2

Se a condição que estou testando for realmente uma má notícia, eu faço o seguinte:

*(int*) NULL= 0;

Isso me dá um bom coredump de onde posso examinar a situação.


4
Isso pode ser otimizado, pois causa um comportamento indefinido. De fato, todo o ramo de execução que possui essa instrução pode ser eliminado pelo compilador.
Ruslan

-2

Para quebrar uma condição, use o retorno (0);

Então, no seu caso, seria:

    if(x==1)
    {
        return 0;
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.