paulo1205
(usa Ubuntu)
Enviado em 23/03/2018 - 10:25h
Essa diferença é filosófica e, em certos casos, depende do contexto.
Começando pelo aspecto etimológico, “fatal” significa “inevitável”, “que mata”, “que põe fim” e “que causa prejuízo”. Isso, por si só, já dá uma ideia do que “erro fatal” pode vir a ser.
Geralmente, num contexto amplo de programação, um erro é qualquer circunstância que impeça o programa de seguir o fluxo de execução desejável.
Entre os possíveis erros, há muitos que podem ser contornados com relativa facilidade, sem afetar de modo nocivo outras partes do programa nem do sistema, nem provocar outras consequências indesejáveis. Por exemplo, quando um programa espera receber um valor numérico, mas o usuário digita caracteres que não podem ser interpretados como partes de um número, o que se tem é um erro, pois o programa não pode aceitar aquele dado para prosseguir com a computação. Nesse caso, contudo, é geralmente muito simples identificar o preenchimento incorreto, de modo que se pode mostrar ao usuário uma mensagem de erro, e pedir que ele refaça a operação, a fim de que a computação possa prosseguir com sucesso.
Há, porém, casos em que o erro pode não ser facilmente recuperável, ou isso pode até mesmo ser impossível com os recursos de que o programa dispõe. Existem também situações em que o programa identifica que o prosseguimento da execução poderia provocar perda de dados ou impactar outros usuários ou serviços da máquina. Nesses casos, como a computação está impossibilitada de prosseguir ou poderia provocar prejuízos futuros ou a terceiros, o que geralmente se faz é interromper o programa completamente, abortando sua execução como forma de evitar desperdícios e minimizar danos.
Um exemplo de erro que pode ser considerado fatal: suponha que o mesmo programa hipotético que espera um valor numérico perceba que o resultado da operação de leitura foi um fim prematuro dos dados. Nesse caso, não adianta pedir ao usuário para redigitar o valor, pois o sistema já sinalizou que não haverá mais como receber dados por aquela fonte. Se aquele dado for fundamental para o prosseguimento do programa, então o mais sensato é não prosseguir com o programa de modo nenhum.
Outro exemplo poderia ser o seguinte: um usuário vai processar um grande volume de dados, a tal ponto de precisar ficar alguns dias computando, produzindo como saída um ou mais arquivos com dados intermediários e, depois, com dados em forma final. Se, por acaso, faltar espaço em disco durante a geração de um dos arquivos de saída, não adianta ignorar o erro resultante dessa falta de espaço, pois isso acarretaria a produção de dados corrompidos, além de um grande desperdício de tempo de computação.
Esse é, aliás, um possível benefício de tratar um erro como fatal: chamar a atenção do usuário para o erro pode incentivá-lo, a corrigir situações de erro externas ao programa, ou a providenciar ou solicitar uma melhoria no programa que o faça tratar de modo mais suave uma situação que pode não ser tão anormal a ponto de ser considerada fatal.
----
Agora, se você pensar no contexto do C e do C++, eis uma particularização de contexto da discussão geral que vai acima. Alguns pontos relevantes que me vêm à mente agora são:
• A biblioteca padrão do C oferece uma função para indicar erros fatais ao sistema, chamada
abort(). O resultado da chamada a essa função é geralmente terminar o programa, mas sem os mesmos cuidados tomados por
exit(), tais como fechar arquivos que ainda estejam abertos, chamar funções registradas com
atexit() e invocar código de desconstrução de objetos globais.
• A biblioteca padrão de funções do C, até onde me lembro, não trata nenhum erro como fatal, mas sinaliza todas as situações que pode para que o usuário decida como tratar.
• Por não forçar erros fatais ao programa, uma falha muito comum em programas em C é não testar possíveis indicações de erro em operações que eventualmente podem falhar, tais como alocação de memória (
malloc(), mas principalmente
realloc()), operações de escrita (todas elas, incluindo escrita para o usuário com
printf(); quase ninguém testa o valor de retorno de operações de escrita), sucesso na leitura e adequação de formato e de valor dos dados de entrada (
scanf(), por exemplo, é uma função muito complexa, podendo sair com diferentes tipos de erro e com sucesso de execução apenas parcial).
• Mesmo que a biblioteca padrão do C não force o programador a testar erros, alguns tipos de erro podem ser forçados ao programa pelo sistema operacional ou ambiente de execução — fora, portanto, daquilo que o padrão
do C estritamente prescreve —, e o tratamento de alguns desses erros pode ser o de os considerar como fatais, o que implica o fim possivelmente prematuro da execução do programa. Alguns exemplos de erros assim são divisão por zero, tentativa de acesso a endereço de memória inválido, falha em operações que envolvam comunicação por rede ou entre processos diferentes, tentativa de executar uma instrução que o processador não suporta etc. No mundo UNIX, esses erros são reportados ao programa através do mecanismo de sinais, e existem funções que permitem ao programador interceptar tais sinais, potencialmente fazendo com que tais erros deixem de ser tratados como fatais. (O Windows usa um mecanismo diferente do de sinais; se não me engano, a terminologia usada no Windows os chama de exceções — tais exceções do Windows, contudo, não são a mesma coisa que o mecanismo de exceções do C++, e é importante não confundir os dois.)
• Ao contrário do C, o C++ possui um mecanismo de sinalização de erros que trata como fatais todos os erros que não sejam explicitamente interceptados pelo programador. Esse mecanismo se baseia no disparo de “exceções”, que podem carregar valores informativos a respeito da circunstância ou do tipo do erro, e inclui provisões para ajudar a liberar, com pouco esforço para o programador, recursos que tenham sido reservados ao longo da operação que acabou falhando.
• Operações de alocação de memória em C++, realizadas por meio do operador
new, usam exceções por padrão, mas seu uso pode ser desabilitado, fazendo com que o comportamento de
new fique mais parecido com o de
malloc() (i.e. uma operação de alocação que falhe devolve um ponteiro nulo,). Por outro lado, operações de entrada e saída não empregam exceções por padrão, mas elas podem ser ligadas em cada objeto que for criado para realizar tais operações.
• Exceções do C++ geralmente estão ligadas a algum objeto específico dentro do programa. Sinais do UNIX (e creio também que as exceções do Windows), por outro lado, ainda que aconteçam como resultado da manipulação de um objeto em particular, são enviadas ao programa como um todo. Pode ser muito difícil, particularmente em programas com múltiplas threads de execução, saber quem exatamente provocou um erro.