Quão importante você considera a segurança de exceção no seu código C ++?

19

Toda vez que considero tornar meu código uma exceção fortemente segura, justifico não fazê-lo, pois isso consumiria muito tempo. Considere este trecho relativamente simples:

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

Para implementar uma garantia básica de exceção, eu poderia usar um ponteiro com escopo definido nas newchamadas. Isso impediria vazamentos de memória se alguma das chamadas lançasse uma exceção.

No entanto, digamos que eu queira implementar uma forte garantia de exceção. No mínimo, eu precisaria implementar um ponteiro compartilhado para meus contêineres (não estou usando o Boost), uma janela Entity::Swappara adicionar os componentes atomicamente e algum tipo de idioma para adicionar atomicamente ao Vectore Map. Isso não apenas levaria tempo para implementar, mas também seria caro, pois envolve muito mais cópia do que a solução insegura da exceção.

Por fim, parece-me que o tempo gasto fazendo tudo isso não seria justificado apenas para que a CreateEntityfunção simples seja fortemente segura contra exceções. Eu provavelmente só quero que o jogo mostre um erro e feche nesse ponto de qualquer maneira.

Até onde você leva isso em seus próprios projetos de jogos? É geralmente aceitável escrever um código não seguro de exceção para um programa que pode travar quando há uma exceção?

Kai
fonte
21
Quando eu trabalhei em projetos C ++ no passado, basicamente fingimos que não havia exceções.
Tétrada
2
Eu, pessoalmente, amo exceções e geralmente gosto de descobrir como fazer meu código fornecer fortes garantias. Se você apenas travar quando receber uma exceção ... talvez não se incomode com isso.
7
Eu pessoalmente uso exceções para gerenciar .. hm .. 'exceções' não erros. Tipo, se eu sei que uma função pode travar, eu a deixo travar, mas se eu sei que uma função pode não ler um arquivo com êxito, mas há outra maneira, eu o envolvo com uma tentativa, captura. e se for uma exceção "incapaz de ler", eu apenas gerencio da outra maneira que faria sem a leitura do arquivo.
Gustavo Maciel
O que Gtoknu disse, você precisa estar ciente de quais exceções quaisquer bibliotecas externas podem lançar.
precisa saber é o seguinte

Respostas:

26

Se você usar exceções (o que não significa que você deva, existem prós e contras nessa abordagem que estão fora do escopo específico desta pergunta), você deve escrever um código seguro de exceção corretamente.

O código que não usa exceções e não é explicitamente seguro contra exceções é melhor do que o código que as utiliza, mas está meio-assegurando sua segurança de exceção. Se você tiver o último caso, terá toda uma classe de bugs e falhas que podem ocorrer quando ocorre uma exceção da qual você provavelmente não consegue se recuperar, tornando um dos benefícios das exceções totalmente nulos. Mesmo que seja uma classe relativamente rara de falhas, ainda é possível.

Isso não quer dizer que, se você usa exceções, todo o código deve fornecer a garantia de exceção forte - apenas que cada parte do código deve fornecer uma garantia de algum tipo (nenhuma, básica, forte) para que, no consumo desse código você sabe o que garante que o consumidor pode fornecer. Geralmente, quanto menor o componente em questão, mais forte deve ser a garantia.

Se você nunca vai fornecer uma garantia forte ou nunca vai realmente abraçar os paradigmas de tratamento de exceções e todo o trabalho e desvantagens extras que isso implica, você também não estará realmente obtendo todas as vantagens que isso implica e pode ser mais fácil tempo apenas renunciando exceções por completo.


fonte
12
Só isso vale um +1: "O código que não usa exceções e explicitamente não é seguro para exceções é melhor que o código que as utiliza, mas está meio que assegurando sua segurança de exceção".
Maximus Minimus
14

Minha regra geral: se você precisar usar exceções, use exceções. Se você não precisar usar exceções, não use exceções. Se você não sabe se precisa ou não usar exceções, não precisa usar exceções.

Exceções são a sua última linha de defesa absoluta em aplicativos de missão crítica sempre ativos. Se você estiver escrevendo um software de controle de tráfego aéreo que absolutamente nunca pode falhar, use exceções. Se você estiver escrevendo um código de controle para uma usina nuclear, use exceções.

Ao desenvolver um jogo, por outro lado, é muito melhor seguir o mantra: travar cedo, travar alto. Se houver um problema, você realmente deseja saber o mais rápido possível; você nunca deseja que os erros sejam invisivelmente "manipulados", porque isso geralmente oculta a causa real de comportamentos posteriores e torna a depuração muito mais difícil do que precisa.

Trevor Powell
fonte
5

O problema das exceções é que você precisa lidar com elas de qualquer maneira. Se você está recebendo uma exceção por falta de memória, pode ser possível que você não consiga lidar com esse erro de qualquer maneira. É melhor simplesmente despejar o jogo inteiro em um manipulador de exceção gigante que exibe uma caixa de erro.

Para o desenvolvimento de jogos, prefiro tentar encontrar maneiras de fazer meu código continuar graciosamente com falhas sempre que possível.

Por exemplo, codificarei uma malha especial de erro no meu jogo. É um círculo vermelho rotativo com um X branco e uma borda branca. Dependendo do jogo, um invisível pode ser melhor. Ele tem uma instância singleton inicializada no início. Como está inserido no código, em vez de usar arquivos externos, não deve ser possível falhar.

No caso de uma chamada loadMesh normal falhar, em vez de retornar algum erro e travar na área de trabalho, ela registrará silenciosamente a falha no console / arquivo de log e retornará a malha de erro codificada. Eu descobri que Skyrim realmente faz a mesma coisa quando um mod estraga tudo. Há uma boa possibilidade de que o jogador nem veja a malha de erro (a menos que haja algum problema grave e muitos deles sejam assim).

Também existe um shader de fallback. Um que faz operações básicas da matriz de vértices, mas se não houver uma matriz uniforme por algum motivo, ainda deve fazer a vista ortogonal. Ele não possui sombreamento / iluminação / materiais adequados (embora tenha um sinalizador color_overide para que eu possa usá-lo com minha malha de erro).

O único problema possível é se a versão de malha de erro pode interromper permanentemente o jogo de alguma forma. Por exemplo, assegure-se de que não aplique a física, pois se o jogador carregar uma área, a malha de erro chuta nela poderá cair no chão e, se dessa vez, recarregar o jogo com a malha correta funcionando, se for maior, pode ser incorporado no chão. Isso não deve ser muito difícil, pois você provavelmente vai armazenar suas malhas físicas com as gráficas. Os scripts também podem ser um problema. Com algumas coisas, você apenas terá que morrer com força. Não tenho muita certeza de qual seria o uso de um 'nível' de retorno (embora você possa fazer uma pequena sala com uma placa dizendo que houve um erro, apenas verifique se o salvamento está desativado, pois você não quer que o jogador fique preso nele).

No caso de 'novo', para o desenvolvimento de jogos, pode ser melhor usar algum tipo de fábrica. Ele pode oferecer várias outras vantagens, como pré-alocar objetos antes que você realmente precise deles e / ou torná-los em massa. Também facilita a criação de coisas como um índice global de objetos que você pode usar para gerenciamento de memória, localizando as coisas por um ID (embora você possa fazer isso também nos construtores). E é claro que você também pode lidar com erros de alocação.

David C. Bishop
fonte
2

A grande coisa sobre a falta de verificações e falhas no tempo de execução é o potencial de um hacker usar uma falha para assumir o processo e, talvez, posteriormente a máquina.

Agora, um hacker não pode explorar uma falha real, assim que ocorre, o processo é inútil e inofensivo. Se você simplesmente ler de um ponteiro nulo, é uma falha limpa, cometerá um erro e o processo morrerá instantaneamente.

O perigo surge quando você executa um comando que não é tecnicamente ilegal, mas não era o que você pretendia fazer; então, um hacker poderá direcionar essa ação usando dados cuidadosamente criados que, de outra forma, deveriam ser seguros.

Uma exceção não capturada não é realmente uma falha real, é apenas uma maneira de encerrar o programa. Exceções só acontecem porque alguém escreveu uma biblioteca que lança especificamente uma exceção, dadas determinadas circunstâncias. Normalmente, para evitar entrar em uma situação inesperada, o que pode ser uma abertura para um hacker.

Na verdade, a ação perigosa é capturar exceções, em vez de deixá-las escapar, ou seja, você nunca deve fazê-lo, mas certifique-se de capturar apenas aquelas que sabe que pode lidar e deixar o resto escapar. Caso contrário, você corre o risco de anular a medida de segurança cuidadosamente colocada em sua biblioteca.

Concentre-se nos erros que não geram necessariamente exceções, como escrever além do final de uma matriz. Seu programa não poderia, por acaso, fazer isso?

aaaaaaaaaaaa
fonte
2

Você precisa examinar suas exceções e as condições que podem acioná-las. Muito tempo, uma grande porcentagem do que as pessoas usam exceções pode ser evitada por verificações adequadas durante o ciclo de desenvolvimento / depuração / teste. Use asserts, passe referências, sempre inicialize variáveis ​​locais na declaração, memset-zero todas as alocações, NULL após liberações, etc, e muitos casos de exceção teóricos simplesmente desaparecem.

Considere: se algo falhar e puder ser um candidato a lançar uma exceção - qual é o impacto? Quanto isso importa? Se você falha na alocação de memória em um jogo (para pegar o exemplo original), geralmente você tem um problema maior em suas mãos do que como lida com o caso de falha, e lidar com o caso de falha por meio de uma exceção não causará esse problema maior vá embora. Pior - pode até esconder o fato de que o problema maior ainda existe. Você quer isso? Eu sei que não. Sei que, se falhar em uma alocação de memória, quero travar e queimar horrivelmente e fazê-lo o mais próximo possível do ponto de falha, para poder entrar no depurador, descobrir o que está acontecendo e corrigi-lo corretamente.

Maximus Minimus
fonte
1

Não tenho certeza se citar alguém é apropriado no SE, mas sinto que nenhuma das outras respostas realmente abordou isso.

Citando Jason Gregory de seu livro Game Engine Architecture . (GRANDE LIVRO!)

"O tratamento de exceção estruturada (SEH) adiciona muita sobrecarga ao programa. Todo quadro de pilha deve ser aumentado para conter informações adicionais exigidas pelo processo de desenrolamento de pilha. Além disso, o desenrolamento de pilha geralmente é muito lento - da ordem de duas a três vezes mais caro do que simplesmente retornar da função. Além disso, se apenas uma função do seu programa (ou biblioteca) usa o SEH, todo o programa deve usar o SEH. O compilador não pode saber quais funções podem estar acima de você na pilha de chamadas quando você lança uma exceção. "

Com isso dito, meu motor que estou construindo não usa exceções. Isso ocorre devido ao custo potencial de desempenho mencionado anteriormente e ao fato de que, para todos os meus propósitos, os códigos de retorno de erro funcionam bem. O único momento em que realmente preciso procurar falhas é inicializar e carregar recursos e, nesses casos, retornar um valor booleano indicando sucesso funciona bem.

KlashnikovKid
fonte
2
O tratamento de exceções estruturado é um tipo específico de tratamento de exceção disponível no Windows, que não é o mesmo que as exceções de C ++ em geral.
Kylotan
Não acredito que não sabia disso. Obrigado pela correção!
precisa saber é o seguinte
0

Eu sempre faço minha exceção de código segura, simplesmente porque facilita saber onde surgem os problemas, pois posso registrar algo sempre que uma exceção é gerada ou capturada.

carddamom
fonte