Um programa C ++ deve capturar todas as exceções e impedir que as exceções borbulhem após main ()?

29

Uma vez fui avisado de que um programa C ++ deveria capturar todas as exceções. O raciocínio dado na época era essencialmente o de programas que permitem que exceções surjam fora de main()um estado zumbi estranho. Foi-me dito isso há vários anos e, em retrospecto, acredito que o fenômeno observado deveu-se à geração prolongada de despejos de núcleo excepcionalmente grandes do projeto em questão.

Na época, isso parecia bizarro, mas convincente. Era totalmente absurdo que o C ++ deveria "punir" os programadores por não capturar todas as exceções, mas as evidências diante de mim pareciam confirmar isso. Para o projeto em questão, os programas que lançaram exceções não capturadas pareciam entrar em um estado zumbi estranho - ou, como suspeito que a causa fosse agora, um processo no meio de um despejo de núcleo indesejado é extraordinariamente difícil de parar.

(Para quem se pergunta por que isso não era mais óbvio na época: o projeto gerou uma grande quantidade de saída em vários arquivos de vários processos que efetivamente obscureceram qualquer tipo de aborted (core dumped)mensagem e, nesse caso em particular, o exame post mortem dos core dumps não foi É uma técnica importante de depuração, para que os despejos principais não sejam levados em consideração.Os problemas com um programa geralmente não dependem do estado acumulado de muitos eventos ao longo do tempo por um programa de longa duração, mas das entradas iniciais de um programa de curta duração (< 1 hora), por isso era mais prático executar novamente um programa com as mesmas entradas de uma compilação de depuração ou em um depurador para obter mais informações.)

Atualmente, não tenho certeza se existe alguma grande vantagem ou desvantagem de capturar exceções apenas com o objetivo de impedir a saída de exceções main().

A pequena vantagem em que posso pensar em permitir que as exceções surjam main()é que ele faz com que o resultado std::exception::what()seja impresso no terminal (pelo menos nos programas compilados pelo gcc no Linux). Por outro lado, isso é fácil de obter, capturando todas as exceções derivadas std::exceptione imprimindo o resultado std::exception::what()e, se é desejável imprimir uma mensagem de uma exceção que não deriva std::exception, ela deve ser capturada antes de sair main()para imprimir a mensagem.

A modesta desvantagem em que consigo pensar em permitir que exceções passem despercebidas main()é que podem ser gerados despejos de núcleo indesejados. Para um processo que utiliza uma grande quantidade de memória, isso pode ser um incômodo e o controle do comportamento de dumping do núcleo de um programa requer chamadas de função específicas do SO. Por outro lado, se um dump e saída de núcleo forem desejados, isso poderá ser alcançado a qualquer momento chamando std::abort()e uma saída sem dump de núcleo poderá ser alcançada a qualquer momento chamando std::exit().

Curiosamente, acho que nunca vi a what(): ...mensagem padrão impressa por um programa amplamente distribuído ao travar.

Quais são os argumentos fortes, a favor ou contra, para permitir que as exceções do C ++ passem despercebidas main()?

Edit: Existem muitas perguntas gerais sobre manipulação de exceções neste site. Minha pergunta é especificamente sobre exceções de C ++ que não podem ser tratadas e foram feitas até o fim main()- talvez uma mensagem de erro possa ser impressa, mas é um erro de parada imediatamente exibido.

Praxeolitic
fonte
@gnat Minha pergunta é um pouco mais específica. Trata-se de exceções em C ++ que não podem ser tratadas, em vez de manipulação de exceções em qualquer linguagem de programação em nenhuma circunstância.
Praxeolitic
Para programas multi-threaded, as coisas podem ser mais complexa ou mais exótico (exceções WRT)
Basile Starynkevitch
1
Os caras de software para o Ariane 5 , certamente, gostaria que eles tivessem apanhado todas as exceções durante o primeiro lançamento ...
Eric Torres

Respostas:

25

Um problema ao deixar as exceções passarem do ponto principal é que o programa terminará com uma chamada para a std::terminatequal o comportamento padrão é chamar std::abort. É apenas uma implementação definida se o desenrolamento da pilha for feito antes da chamada, terminatepara que seu programa possa terminar sem chamar um único destruidor! Se você tem algum recurso que realmente precisava ser restaurado por uma chamada de destruidor, está em apuros ...

erlc
fonte
12
Se você tem algum recurso que realmente precisava ser restaurado por uma chamada de destruidor, você está em apuros ... => Dado que (infelizmente) o C ++ é bastante propenso a falhas e isso sem mencionar que o sistema operacional pode decidir matar seu programa a qualquer momento (OOM killer no Unix, por exemplo), seu programa (e seu ambiente) precisa ser adaptado para suportar falhas.
precisa
@MatthieuM. Você está dizendo que os destruidores não são um bom lugar para a limpeza de recursos que deve ocorrer mesmo durante uma falha?
Praxeolitic
6
@Praxeolitic: estou dizendo que nem todas as falhas desenrolam a pilha, por exemplo std::abort, não e, portanto, afirmações com falha também não. Também vale a pena observar que, nos sistemas operacionais modernos, o próprio sistema operacional limpará muitos recursos (memória, identificadores de arquivos, ...) vinculados ao ID do processo. Por fim, eu também estava sugerindo "Defense in Depth": não é confiável esperar que todos os outros processos sejam à prova de erros e sempre libere os recursos que adquiriram (sessões, finalize bem a gravação de arquivos, ...) que você deve planejar. -lo ...
Matthieu M.
3
@Praxeolitic Não, seu software precisa não corromper dados durante uma falha de energia. E se você puder gerenciar isso, também poderá não corromper os dados após uma exceção não tratada.
precisa saber é o seguinte
1
@Praxeolitic: Na verdade, isso existe. Você quer ouvir a WM_POWERBROADCASTmensagem. Isso funciona apenas se o computador estiver usando bateria (se você estiver usando um laptop ou um no-break).
5277 Brian
28

O principal motivo para não deixar escapar exceções mainé porque, caso contrário, você perde toda a possibilidade de controlar como o problema é relatado aos usuários.

Para um programa que não se destina a ser usado por muito tempo ou amplamente distribuído, pode ser aceitável que erros inesperados sejam relatados da maneira que o sistema operacional decidir fazer (por exemplo, mostrando uma caixa de diálogo de erro na sua cara no Windows )

Para os programas que você vende, ou que são fornecidos ao público em geral por uma organização que tem uma reputação a defender, geralmente é uma idéia melhor relatar de maneira agradável que você encontrou um problema inesperado e tentar economizar o máximo possível. dados do usuário quanto possível. Não perder meio dia de trabalho do usuário nem travar inesperadamente, mas encerrar sem graça é geralmente muito melhor para a reputação da empresa do que a alternativa.

Bart van Ingen Schenau
fonte
Você tem certeza de que o comportamento padrão de uma exceção C ++ que sai main() tem muito a ver com o sistema operacional? O sistema operacional pode não saber nada de C ++. Meu palpite era que ele é determinado pelo código que o compilador insere em algum lugar do programa.
Praxeolitic
5
@Praxeolitic: é mais que o sistema operacional define convenções e o compilador gera código para cumprir essas convenções quando um programa termina inesperadamente. O ponto principal é que o encerramento inesperado do programa deve ser evitado sempre que razoavelmente possível.
Bart van Ingen Schenau
Você perde apenas o controle no escopo do C ++ padrão. Uma maneira específica de implementação de lidar com essas "falhas" deve estar disponível. O C ++ geralmente não é executado no vácuo.
Martin Ba
Essa caixa de diálogo de erro na cara pode ser configurada como desativada no registro para o que vale a pena - mas você precisaria ter controle do registro para fazer isso - e a maioria dos programas não está sendo executada no modo quiosque (tendo assumido o controle o SO).
56016 PerryC
11

TL; DR : O que a especificação diz?


Um desvio técnico ...

Quando uma exceção é lançada e nenhum manipulador está pronto para isso:

  • é implementação definida se a pilha está desenrolada ou não
  • std::terminate é chamado, que por padrão aborta
  • Dependendo da configuração do seu ambiente, o cancelamento pode ou não deixar um relatório de falha para trás

O último pode ser útil para erros muito pouco frequentes (porque reproduzi-los é um processo que desperdiça tempo).


Capturar todas as exceções ou não é, em última análise, uma questão de especificação:

  • está especificado como relatar erros do usuário? (Valor inválido, ...)
  • está especificado como relatar erros funcionais? (diretório de destino ausente, ...)
  • está especificado como relatar erros técnicos? (afirmação iniciando, ...)

Para qualquer programa de produção, isso deve ser especificado e você deve seguir a especificação (e talvez argumentar que ela foi alterada).

Para programas reunidos rapidamente e serem usados ​​apenas por pessoas técnicas (você, seus colegas de equipe), tudo bem. Eu recomendo deixá-lo travar e configurar o ambiente para obter um relatório ou não, dependendo de suas necessidades.

Matthieu M.
fonte
1
Este. Os fatores decisivos Estão basicamente fora do escopo do C ++.
Martin Ba
4

Uma exceção que você captura oferece a oportunidade de imprimir uma boa mensagem de erro ou até mesmo tentar se recuperar do erro (possivelmente apenas reiniciando o aplicativo).

No entanto, em C ++, uma exceção não contém informações sobre o estado do programa quando foi lançado. Se você o capturar, todo esse estado será esquecido, enquanto que se você deixar o programa travar, o estado ainda estará lá e poderá ser lido no dump do programa, facilitando a depuração.

Portanto, é uma troca.

Sebastian Redl
fonte
4

No momento em que você souber que precisa abortar, vá em frente e ligue std::terminatejá para reduzir qualquer dano adicional.

Se você sabe que pode relaxar com segurança, faça isso. Lembre-se de que o desenrolamento de pilha não é garantido quando uma exceção nunca é capturada;

Se você puder relatar / registrar com segurança o erro melhor do que o sistema por si só, vá em frente.
Mas certifique-se de não piorar inadvertidamente as coisas ao fazê-lo.

Muitas vezes, é tarde demais para salvar dados quando você detecta um erro irrecuperável, embora isso dependa de um erro específico.
De qualquer forma, se o seu programa foi escrito para recuperação rápida, apenas matá-lo pode ser a melhor maneira de finalizá-lo, mesmo que seja apenas um desligamento normal.

Desduplicador
fonte
1

Falhando graciosamente é uma coisa boa na maioria das vezes - mas existem trade-offs. Às vezes, é uma boa coisa travar. Devo mencionar que estou pensando principalmente na depuração em grande escala. Para um programa simples - embora isso ainda possa ser útil, não é nem de longe tão útil quanto um programa muito complexo (ou vários programas complexos interagindo).

Você não quer travar em público (embora isso seja realmente inevitável - os programas travam e um programa sem travamento, matematicamente verificável, não é o que estamos falando aqui). Pense em Bill Gates BSODing no meio de uma demonstração - ruim, certo? No entanto, podemos tentar descobrir por que caímos e não caímos novamente da mesma maneira.

Ativei um recurso do Relatório de Erros do Windows que cria despejos de falhas locais em exceções não tratadas. É maravilhoso saber se você possui os arquivos de símbolos associados à sua compilação (travando). Curiosamente, porque configurei essa ferramenta, quero travar mais - porque aprendo com cada travamento. Então eu posso consertar os bugs e travar menos.

Então, por enquanto, quero que meus programas cheguem ao topo e falhem. No futuro, talvez eu queira comer todas as minhas exceções, mas não se puder fazê-las funcionar para mim.

Você pode ler mais sobre despejos de falhas locais aqui se estiver interessado: Coletando despejos no modo de usuário

PerryC
fonte
-6

Por fim, se uma exceção aparecer acima de main (), ela travará seu aplicativo e, em minha opinião, um aplicativo nunca deve travar. Se não há problema em travar um aplicativo em um só lugar, por que não em qualquer lugar? Por que se preocupar com o tratamento de exceções? (Sarcasmo, realmente não estou sugerindo isso ...)

Você pode ter uma tentativa / captura global que imprime uma mensagem elegante informando ao usuário que ocorreu um erro irrecuperável e que eles precisam enviar um e-mail para a TI sobre isso ou algo semelhante, mas a falha é completamente não profissional e inaceitável. É trivial prevenir e você pode informar um usuário sobre o que acabou de acontecer e como consertar as coisas para que isso não aconteça novamente.

Bater é estritamente hora amadora.

Roger Hill
fonte
8
Então, é melhor fingir que tudo está funcionando corretamente e substituir todo o armazenamento permanente com sem sentido?
Deduplicator
1
@ Reduplicador: Não: você sempre pode capturar a exceção e lidar com ela.
Giorgio
5
Se você souber como lidar com isso, provavelmente o manipulará muito antes de começar main. Se você não sabe como lidar com isso, fingir que você é obviamente um bug realmente horrível.
Deduplicator
8
mas travar é completamente não profissional e inaceitável => gastar tempo trabalhando em recursos inúteis não é profissional: travar só importa se for importante para o usuário final, se não for, e depois deixá-lo travar (e obter um relatório de travamento, com um despejo de memória) é mais econômico.
precisa
Matthieu M. Se você realmente se esforçar muito para registrar um erro, salvar todos os arquivos abertos e pintar uma mensagem amigável na tela, não sei o que dizer. Se você acha que é inútil fracassar normalmente, provavelmente converse com quem estiver fazendo o suporte técnico para o seu código.
Roger Hill