Como lançar uma exceção em C?

100

Eu digitei isso no google, mas só encontrei howtos em C ++,

como fazer em C?

httpinterpret
fonte
6
C não suporta manipulação de exceção. Para lançar uma exceção em C, você precisa usar algo específico da plataforma, como o tratamento de exceção estruturado do Win32 - mas para dar alguma ajuda com isso, precisaremos saber a plataforma que você gosta.
Jerry Coffin
18
... e não use o tratamento de exceção estruturado do Win32.
Brian R. Bondy
4
Usar setjmp () e longjmp () deveria, em teoria, funcionar, mas não acho que valha a pena.
Joseph Quinsey

Respostas:

77

Não há exceções em C. Em C os erros são notificados pelo valor retornado da função, o valor de saída do processo, sinais para o processo ( sinais de erro do programa (GNU libc) ) ou a interrupção do hardware da CPU (ou outra notificação erro da CPU, se houver) ( Como o processador trata o caso de divisão por zero ).

As exceções são definidas em C ++ e outras linguagens. O tratamento de exceções em C ++ é especificado no padrão C ++ "Tratamento de exceções S.15", não há seção equivalente no padrão C.

Brian R. Bondy
fonte
4
Portanto, em C é garantido que não haverá exceções, como?
httpinterpret
62
@httpinterpret: em C é "garantido" que não haja exceções da mesma forma que é "garantido" que não haja modelos, ou reflexão, ou unicórnios. A especificação da linguagem simplesmente não define tal coisa. Existe setjmp / longjmp, entretanto, que pode ser usado para sair de uma função sem retornar. Assim, os programas podem construir seus próprios mecanismos de exceção, se quiserem, ou uma implementação em C pode definir extensões para o padrão.
Steve Jessop
2
Um programa mainem um .carquivo pode incluir algum C ++ e, portanto, exceções podem ser lançadas e capturadas no programa, mas as partes do código C permanecerão ignorantes de tudo isso, exceto que o lançamento e a captura de exceções geralmente dependem de funções escritas em C que residem nas bibliotecas C ++. C é usado porque você não pode arriscar que a função chamada throwprecise lançar uma exceção. Pode haver uma maneira específica do compilador / biblioteca / destino para lançar / capturar exceções, no entanto. Mas lançar uma instância de classe terá seus próprios problemas.
nategoose
61
@Steve: Por favor, me diga se você encontrar uma linguagem com unicórnios, há anos que espero por isso.
Brian R. Bondy
5
@ BrianR.Bondy Aqui está uma linguagem com unicórnios (eu garanto da mesma forma que é garantida em C)
Tofandel
37

Em C, você pode usar a combinação das funções setjmp()e longjmp(), definidas em setjmp.h. Exemplo da Wikipedia

#include <stdio.h>
#include <setjmp.h>

static jmp_buf buf;

void second(void) {
    printf("second\n");         // prints
    longjmp(buf,1);             // jumps back to where setjmp 
                                //   was called - making setjmp now return 1
}

void first(void) {
    second();
    printf("first\n");          // does not print
}

int main() {   
    if ( ! setjmp(buf) ) {
        first();                // when executed, setjmp returns 0
    } else {                    // when longjmp jumps back, setjmp returns 1
        printf("main");         // prints
    }

    return 0;
}

Nota: Eu realmente aconselharia você não usá-los porque eles funcionam muito mal com C ++ (destruidores de objetos locais não seriam chamados) e é realmente difícil entender o que está acontecendo. Em vez disso, retorne algum tipo de erro.

vava
fonte
Já vi setjump / longjump não funcionar corretamente em programas C ++, mesmo quando nenhum objeto precisava de destruição quando as exceções eram usadas. Eu raramente os uso em programas C.
nategoose
Esta foi uma abordagem interessante usando setjmp. on-time.com/ddj0011.htm Mas sim, basicamente você tem que inventá-los você mesmo se quiser a execução de código fora da banda sem desfazer a pilha.
Dwayne Robinson
Não sei por que a advertência sobre usá-los em C ++ quando a questão é sobre C e C ++ tem exceções de qualquer maneira. Em qualquer caso, é importante que o OP saiba que, para evitar que a implementação de setjmp / longjmp atire em sua perna, sempre tenha em mente que você precisa primeiro visitar o manipulador de erros (para configurá-lo) e esse manipulador de erros precisa retornar duas vezes.
1
A propósito, o tratamento de exceções era algo que eu realmente esperava que acabasse no C11. Apesar da crença comum, ele não requer OOP para funcionar corretamente, e C sofre infinitamente com cada chamada de função que exige um if(). Não consigo ver perigo nisso; mais alguém pode aqui?
@ user4229245 Estou com a (anedótica) impressão de que qualquer coisa "feita para você" é mantida fora das especificações da linguagem C tanto quanto possível, especificamente para manter c relevante para o bare metal e a programação de máquina onde até mesmo fundamentos do status quo como bytes de oito bits não são é universal, apenas meus pensamentos, não sou um autor c spec
ThorSummoner
21

O C simples e antigo não suporta exceções nativamente.

Você pode usar estratégias alternativas de tratamento de erros, como:

  • retornando um código de erro
  • retornando FALSEe usando uma last_errorvariável ou função.

Consulte http://en.wikibooks.org/wiki/C_Programming/Error_handling .

Lucas jones
fonte
Além disso, a API do Windows tem o mesmo método para lidar com os erros. por exemplo, GetLastError()na API do Windows.
BattleTested
20

Não há mecanismo de exceção embutido em C; você precisa simular exceções e sua semântica. Isso geralmente é conseguido confiando em setjmpelongjmp .

Existem algumas bibliotecas por aí, e estou implementando mais uma. É chamado de exceptions4c ; é portátil e gratuito. Você pode dar uma olhada nele e compará-lo com outras alternativas para ver qual se encaixa mais em você.

Guillermo Calvo
fonte
Eu li seu exemplo de código, comecei um projeto semelhante com uma estrutura, mas usa um uuid em vez de uma string para identificar cada "exceção". Seu projeto parece muito promissor.
umlcat de
O link de alternativas agora deve apontar para github.com/guillermocalvo/exceptions4c/wiki/alternatives
Marcel Waldvogel
Você (ou qualquer outra pessoa) sabe sobre uma comparação entre os diferentes pacotes de exceção?
Marcel Waldvogel
11

C é capaz de lançar a exceção C ++, eles são códigos de máquina de qualquer maneira. Por exemplo, em bar.c

// begin bar.c
#include <stdlib.h>
#include <stdint.h>
extern void *__cxa_allocate_exception(size_t thrown_size);
extern void __cxa_throw (void *thrown_exception, void* *tinfo, void (*dest) (void *) );
extern void * _ZTIl; // typeinfo of long
int bar1()
{
   int64_t * p = (int64_t*)__cxa_allocate_exception(8);
   *p = 1976;
   __cxa_throw(p,&_ZTIl,0);
  return 10;
}
// end bar.c

em a.cc,

#include <stdint.h>
#include <cstdio>
extern "C" int bar1();
void foo()
{
  try{
    bar1();
  }catch(int64_t x){
    printf("good %ld",x);
  }
}
int main(int argc, char *argv[])
{
  foo();
  return 0;
}

para compilá-lo

gcc -o bar.o -c bar.c && g++ a.cc bar.o && ./a.out

resultado

good 1976

http://mentorembedded.github.io/cxx-abi/abi-eh.html tem mais informações detalhadas sobre__cxa_throw .

Não tenho certeza se é portátil ou não, e testei com 'gcc-4.8.2' no Linux.

wcy
fonte
2
O que diabos é "_ZTIl" ??? Não consigo encontrar qualquer referência a ele em qualquer lugar e é um mistério completo como isso tornaria possível para o código C ter acesso a std :: type_info. Por favor me ajude!
AreusAstarte
1
_ZTIIsignifica typeinfo for long, por exemplo echo '_ZTIl' | c++filt . É o esquema de mutilação g ++.
wcy
e apenas por precaução, certifique-se de chamar o símbolo ac no bloco catch para evitar c ++ tanto quanto possível: P (PS, obrigado por compartilhar um excelente exemplo real!)
ThorSummoner
8

Esta pergunta é muito antiga, mas acabei de topar com ela e pensei em compartilhar uma técnica: dividir por zero ou remover a referência de um ponteiro nulo.

A questão é simplesmente "como lançar", não como pegar, ou mesmo como lançar um tipo específico de exceção. Tive uma situação há muito tempo em que precisávamos acionar uma exceção de C para ser detectada em C ++. Especificamente, tivemos relatórios ocasionais de erros de "chamada de função virtual pura" e precisávamos convencer a função _purecall do tempo de execução C a lançar algo. Então, adicionamos nossa própria função _purecall, que é dividida por zero, e bum, tivemos uma exceção de que poderíamos pegar em C ++ e até mesmo usar um pouco de diversão de pilha para ver onde as coisas deram errado.

Joe
fonte
@AreusAstarte apenas no caso de alguém realmente precisar ver uma divisão C por zero:int div_by_nil(int x) { return x/0; }
7

No Win com MSVC existe, __try ... __except ...mas é realmente horrível e você não vai querer usá-lo se puder evitá-lo. Melhor dizer que não há exceções.

Donal Fellows
fonte
1
Na verdade, as exceções do C ++ são criadas na parte superior do SEH também nos bastidores.
Calmarius
@Calmarius O que é SEH ?
Marcel Waldvogel
1
@MarcelWaldvogel Structured Exception Handling (Maneira do Windows para lidar com exceções de CPU).
Calmarius
6

C não tem exceções.

Existem várias implementações de hacky que tentam fazer isso (um exemplo em: http://adomas.org/excc/ ).

Joe
fonte
3

Conforme mencionado em vários tópicos, a maneira "padrão" de fazer isso é usando setjmp / longjmp. Postei outra solução desse tipo em https://github.com/psevon/exceptions-and-raii-in-c. Que eu saiba, essa é a única solução que depende da limpeza automática dos recursos alocados. Ele implementa smartpointers exclusivos e compartilhados e permite que funções intermediárias deixem as exceções passarem sem serem detectadas e ainda tenham seus recursos alocados localmente limpos de forma adequada.

user3510229
fonte
2

C não oferece suporte a exceções. Você pode tentar compilar seu código C como C ++ com Visual Studio ou G ++ e ver se ele compilará como está. A maioria dos aplicativos C compilará como C ++ sem grandes alterações, e você pode usar a sintaxe try ... catch.

Mahmoud Al-Qudsi
fonte
0

Se você escrever código com o padrão de design de caminho feliz (ou seja, para dispositivo embutido), você pode simular o processamento de erro de exceção (também conhecido como adiamento ou finalmente emulação) com o operador "goto".

int process(int port)
{
    int rc;
    int fd1;
    int fd2;

    fd1 = open("/dev/...", ...);
    if (fd1 == -1) {
      rc = -1;
      goto out;
    }

    fd2 = open("/dev/...", ...);
    if (fd2 == -1) {
      rc = -1;
      goto out;
    }

    // Do some with fd1 and fd2 for example write(f2, read(fd1))

    rc = 0;

   out:

    //if (rc != 0) {
        (void)close(fd1);
        (void)close(fd2);
    //}

    return rc;
}

Na verdade, ele não faz o handler de exceção, mas mostra uma maneira de lidar com o erro na saída da função.

PS Você deve ter cuidado ao usar goto apenas em escopos iguais ou mais profundos e nunca saltar declaração de variável.

Vitold S.
fonte