Tratamento de exceções na linguagem C

Uma implementação de tratamento de exceções usando a linguagem C, discutindo vários modelos e suas limitações, possíveis vazamentos de memórias, testes comparativos, muitos exemplos, introdução a certos recursos da linguagem, ... É certamente um assunto interessante se você quer expandir seus conhecimentos da linguagem C.

[ Hits: 63.406 ]

Por: Vinícius dos Santos Oliveira em 11/11/2010 | Blog: https://vinipsmaker.github.io/


Quebra de fluxo de execução, goto e setjmp



O tratamento de exceções permite que a lógica do programa seja definida em um bloco, e caso alguma exceção seja disparada, esse fluxo é quebrado. Com a quebra desse fluxo, um novo fluxo ocorre.

Nesse novo fluxo, ocorre a pesquisa de um bloco catch, na ordem em que foram dispostos no código, que possa tratar a exceção. Caso seja um bloco catch compatível, ele é executado.

Diz-se então que esse bloco catch trata a exceção. O mecanismo da linguagem C que permite tal poder expressivo, suficiente para construirmos esse mecanismo é o goto, como mostra o exemplo abaixo:

// try {
  if (b) {
    c = a / b;
    goto end;
  } else {
    // throw();
    goto catch;
  }
// catch
catch:
  printf("Informe um número diferente de 0 para o denominador");
end:

É possível explorar outros recursos para alcançar um resultado similar, mas o goto foi escolhido para que a abordagem incremental prometida no começo desse texto possa ser utilizada. Outro motivo é que se fosse feita opção pelo não uso do goto, não seria possível quebrar o fluxo de execução de forma direta arbitrariamente, que é justamente o efeito almejado.

A limitação dessa abordagem é que com o goto não pode-se realizar saltos entre funções, impedindo que problemas sejam quebrados em funções (caso essas funções disparem exceções). Dessa forma, um dos objetivos de se introduzir o tratamento de exceções, que é melhorar a legibilidade do código, seria prejudicado.

int divide(int a, int b) throw()
{
  if (b) {
    return a / b;
  } else {
    throw();
  }
}

Para resolver o problema, devemos utilizar um recurso que permita o salto entre funções. Felizmente, esse recurso está disponível na linguagem C, através do cabeçalho setjmp.h, que faz parte da biblioteca padrão, maximizando a portabilidade da implementação aqui sugerida.

Esse cabeçalho inclui o tipo jmp_buf (cujas variáveis armazenam informações sobre o ambiente de execução em um certo estado) e as funções setjmp (que guarda informações sobre o estado atual em uma variável jmp_buf) e longjmp (que recria o ambiente de execução descrito em uma variável jmp_buf).

A função setjmp recebe um argumento do tipo jmp_buf e retorna um inteiro, que é zero se o ocorrido foi uma simples chamada de função, ou não zero se o ocorrido foi uma chamada a longjmp, que recriou o ambiente de execução que havia sido registrado pela chamada a função setjmp.

Há algumas restrições do que pode-se fazer com as definições desse cabeçalho, mas ainda sim elas são adequadas ao nosso propósito.

int divide(int a, int b, jmp_buf catch)
{
  if (b) {
    return a / b;
  } else {
    // throw()
    longjmp(catch, 1);
  }
}

int main()
{
  int a, b;
  printf("Informe o divisor e o dividendo\n");
  scanf("%d%d", &a, &b);
  jmp_buf try;
  //try
  if (!setjmp(try)) {
    printf("%i\n", divide(a, b, try));
  // catch
  } else {
    printf("Informe um número diferente de 0 para o denominador\n");
  }
}

No exemplo anterior, é declarada uma variável do tipo jmp_buf identificada por try dentro da função main, então a função setjmp é chamada, salvando o estado de execução atual para a variável try, e retorna 0, para indicar o fato.

Então a função divide é chamada, tendo como argumentos o divisor, o dividendo, e a variável do tipo jmp_buf. Em um caso normal, a função realiza o procedimento de dividir os dois operandos normalmente e imprime o resultado na tela.

Se uma divisão por zero for detectada, a função divide usa a variável jmp_buf como primeiro argumento da função longjmp e o inteiro 1 como segundo argumento para retornar ao estado em que o programa chama a função setjmp e interromper o fluxo do bloco try.

A "mágica" funciona graças a flexibilidade e o poder das funções setjmp e longjmp. O valor de retorno da função setjmp é zero quando o estado atual de execução está sendo salvo, ou o valor do segundo argumento da função longjmp quando o estado está sendo restaurado.

Uma limitação que existe nesse modelo (setjmp e longjmp), é que o comportamento do programa é indefinido se você usa algum recurso (goto) para pular para dentro do bloco try (e catch), pois possíveis exceções que sejam disparadas usarão a variável jmp_buf que contém informações sobre uma ambiente de execução inconsistente com o ambiente atual, ou nenhum ambiente.

Se for feito um pulo para um bloco catch a variável que contém o valor disparado pela exceção provavelmente conterá um valor inválido. Apesar desses problemas, é permitido pular para fora do bloco try (pular para fora do bloco catch não é recomendado), usando return, por exemplo.

Página anterior     Próxima página

Páginas do artigo
   1. Introdução
   2. Tratamento de exceções em C
   3. Quebra de fluxo de execução, goto e setjmp
   4. Exceções associadas a inteiros
   5. Informação sobre os tipos em tempo de execução
   6. Macros
   7. Aninhando exceções
   8. Mais macros
   9. Memory leaks
   10. Uso em dispositivos móveis
   11. Conclusão
Outros artigos deste autor

A história do ogg na web

Aplicativos web em C++ usando o Tufão

História da informática: Um pouco de datas e especificações

Próximas Tecnologias do Sistema GNU/Linux

VLC Media Player

Leitura recomendada

openCertiface: Biométrica Facial em nuvem baseada em software livre

Android NDK: Desmistificando o acesso a códigos nativos em C

Tutorial OpenGL v3.0

Estudando recursividade direta e indireta

Ponteiros - Saindo de Pesadelos

  
Comentários
[1] Comentário enviado por removido em 11/11/2010 - 17:16h

Gostei da aparência do código usando macros para definir os blocos try, catch e throw.

E parabéns pela contribuição de suas idéias!

[2] Comentário enviado por mslomp em 11/11/2010 - 18:17h

permita-me contribuir com conteúdo referente a esse assunto, a quem interessar:
há um tempo atrás postei na seção Scripts um código referente a isso:
http://www.vivaolinux.com.br/script/Excecoes-em-C-atraves-de-trythrowcatch

baseado na questão de um usuário em:
http://www.vivaolinux.com.br/topico/C-C++/C-e-Java

parabéns, um ótimo artigo conceitual e referencial

[3] Comentário enviado por SamL em 12/11/2010 - 12:06h

Cara gostei do artigo, nota 10.
Usar essas macros deixa um código bem mais limpo e organizado.

[4] Comentário enviado por gedarius em 12/11/2010 - 12:06h

ótimo artigo, parabéns!!!!

[5] Comentário enviado por vinipsmaker em 12/11/2010 - 15:47h

@mslomp, vlw, é bom saber que esse assunto interessa a muitos.

E aos outros, agradeço os elogios (críticas também são bem-vindas, caso tenham alguma =D ).

[6] Comentário enviado por mazinsw em 12/11/2010 - 19:41h

valeu pela contribuição, eu não sabia usar macros agora está mais fácil.

[7] Comentário enviado por vinipsmaker em 02/03/2013 - 19:19h

Migrei o código para https://github.com/vinipsmaker/c-except


Contribuir com comentário