Eu estava pensando hoje sobre os blocos try / catch existentes em outras línguas. Pesquisei por um tempo isso, mas sem resultado. Pelo que eu sei, não existe try / catch em C. Porém, existe uma maneira de "simular" eles?
Claro, há assert e outros truques, mas nada como try / catch, que também capturam a exceção levantada. Obrigado
101
Respostas:
C em si não oferece suporte a exceções, mas você pode simulá-las até certo ponto com chamadas
setjmp
elongjmp
.Este site tem um bom tutorial sobre como simular exceções com
setjmp
elongjmp
fonte
try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')};
ele não funcionará certo?Use goto em C para situações semelhantes de tratamento de erros.
Esse é o equivalente mais próximo de exceções que você pode obter em C.
fonte
goto
é mais usado para tratamento de erros, mas e daí? A questão não é sobre o tratamento de erros como tal, mas explicitamente sobre equivalentes try / catch.goto
não é equivalente a try / catch, pois está restrito à mesma função.goto
um mecanismo try / catch usado em uma fonte moderna, amplamente aceita e revisada por pares. Procuregoto
um equivalente de "lançamento" efinish
um equivalente de "captura".Ok, não resisti em responder a isso. Deixe-me primeiro dizer que não acho uma boa ideia simular isso em C, pois é realmente um conceito estranho para C.
Podemos
usarabusar das variáveis de pilha de pré-processamento e locais para dar uso uma versão limitada do C ++ try / lance / catch.Versão 1 (lances de escopo local)
A versão 1 é apenas um lançamento local (não pode sair do escopo da função). Ele depende da capacidade do C99 de declarar variáveis no código (deve funcionar no C89 se a tentativa for a primeira coisa na função).
Esta função apenas cria um var local para saber se houve um erro e usa um goto para pular para o bloco catch.
Por exemplo:
Isso funciona para algo como:
Versão 2 (salto de escopo)
A versão 2 é muito mais complexa, mas basicamente funciona da mesma maneira. Ele usa um salto longo da função atual para o bloco try. O bloco try então usa um if / else para pular o bloco de código para o bloco catch que verifica a variável local para ver se ela deve pegar.
O exemplo expandiu novamente:
Este usa um ponteiro global para que longjmp () saiba qual try foi executado pela última vez. Estamos
usandoabusando da pilha para funções criança também pode ter um bloco try / catch.Usar este código tem vários aspectos negativos (mas é um exercício mental divertido):
fonte
bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}
. Mas a variável local também seria redefinida, então as coisas ficam um pouco fora de controle.No C99,você pode usarsetjmp
/longjmp
para fluxo de controle não local.Dentro de um único escopo, o padrão de codificação genérico e estruturado para C na presença de múltiplas alocações de recursos e múltiplas saídas usa
goto
, como neste exemplo . Isso é semelhante a como C ++ implementa chamadas de destruidor de objetos automáticos por baixo do capô e, se você se empenhar nisso, deve permitir um certo grau de limpeza, mesmo em funções complexas.fonte
Enquanto algumas das outras respostas cobriram os casos simples usando
setjmp
elongjmp
, em um aplicativo real, há duas preocupações que realmente importam.jmp_buf
fará com que isso não funcione.jmp_buf
causará todos os tipos de dor nessa situação.A solução para isso é manter uma pilha de thread local
jmp_buf
que é atualizada conforme você avança. (Acho que é isso que lua usa internamente).Então, em vez disso (da resposta incrível de JaredPar)
Você usaria algo como:
Novamente, uma versão mais realista disso incluiria alguma maneira de armazenar informações de erro no
exception_state
, melhor tratamento deMAX_EXCEPTION_DEPTH
(talvez usando realloc para aumentar o buffer ou algo parecido).AVISO LEGAL: O código acima foi escrito sem qualquer teste. É puramente para que você tenha uma ideia de como estruturar as coisas. Diferentes sistemas e diferentes compiladores precisarão implementar o armazenamento local do thread de maneira diferente. O código provavelmente contém erros de compilação e erros de lógica - então, enquanto você estiver livre para usá-lo como quiser, TESTE-o antes de usá-lo;)
fonte
Uma rápida pesquisa no google produz soluções kludgey como esta, que usam setjmp / longjmp como outros mencionaram. Nada tão simples e elegante quanto o try / catch de C ++ / Java. Sou bastante parcial com a exceção de Ada lidando comigo mesma.
Verifique tudo com as declarações if :)
fonte
Isso pode ser feito com o
setjmp/longjmp
C. P99 tem um conjunto de ferramentas bastante confortável para isso que também é consistente com o novo modelo de rosca do C11.fonte
Esta é outra maneira de lidar com erros em C, que tem mais desempenho do que usar setjmp / longjmp. Infelizmente, não funcionará com o MSVC, mas se usar apenas GCC / Clang for uma opção, você deve considerá-la. Especificamente, ele usa a extensão "rótulo como valor", que permite que você pegue o endereço de um rótulo, armazene-o em um valor e salte para ele incondicionalmente. Vou apresentá-lo usando um exemplo:
Se desejar, você pode refatorar o código comum em define, implementando efetivamente seu próprio sistema de tratamento de erros.
Então o exemplo se torna
fonte
Aviso: o seguinte não é muito bom, mas faz o trabalho.
Uso:
Resultado:
Lembre-se de que isso está usando funções aninhadas e
__COUNTER__
. Você estará seguro se estiver usando o gcc.fonte
Redis usa goto para simular try / catch, IMHO é muito limpo e elegante:
fonte
errno
só deve ser usado logo após a falha na chamada do sistema e não três chamadas depois.Em C, você pode "simular" exceções junto com a "recuperação de objeto" automática por meio do uso manual de if + goto para tratamento explícito de erros.
Costumo escrever código C como o seguinte (resumido para destacar o tratamento de erros):
Isso é ANSI C completamente padrão, separa o tratamento de erros do seu código principal, permite o desenrolamento (manual) da pilha de objetos inicializados de maneira muito semelhante à do C ++ e é completamente óbvio o que está acontecendo aqui. Como você está testando explicitamente a falha em cada ponto, é mais fácil inserir logs específicos ou tratamento de erros em cada lugar em que um erro pode ocorrer.
Se você não se importa com um pouco de magia de macro, pode tornar isso mais conciso enquanto faz outras coisas, como registrar erros com rastreamentos de pilha. Por exemplo:
Claro, isso não é tão elegante quanto exceções C ++ + destruidores. Por exemplo, aninhar várias pilhas de tratamento de erros em uma função dessa maneira não é muito limpo. Em vez disso, você provavelmente gostaria de dividi-las em subfunções independentes que lidam de forma semelhante com erros, inicializar + finalizar explicitamente assim.
Isso também funciona apenas dentro de uma única função e não continuará pulando na pilha a menos que chamadores de nível superior implementem uma lógica de tratamento de erros explícita semelhante, enquanto uma exceção C ++ continuará pulando na pilha até encontrar um manipulador apropriado. Nem permite que você lance um tipo arbitrário, mas apenas um código de erro.
A codificação sistemática dessa forma (ou seja, com uma única entrada e um único ponto de saída) também torna muito fácil inserir a lógica pré e pós ("finalmente") que será executada independentemente do que aconteça. Você acabou de colocar sua lógica "finalmente" após o rótulo END.
fonte
Se estiver usando C com Win32, você pode aproveitar seu Structured Exception Handling (SEH) para simular try / catch.
Se você estiver usando C em plataformas que não suportam
setjmp()
elongjmp()
, dê uma olhada neste Tratamento de exceções da biblioteca pjsip, ele fornece sua própria implementaçãofonte
Talvez não seja um idioma principal (infelizmente), mas em APL, existe a operação ⎕EA (que significa Execute Alternate).
Uso: 'Y' ⎕EA 'X' onde X e Y são trechos de código fornecidos como strings ou nomes de função.
Se X der certo, Y (geralmente tratamento de erros) será executado.
fonte