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: 69.299 ]

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


Informação sobre os tipos em tempo de execução



Em C++, pode-se disparar como exceção qualquer tipo, e essa flexibilidade é desejável em muitos sistemas. Para permitir que o tratamento de exceções desenvolvido nesse texto trabalhe de um modo mais similar ao presente na linguagem C++ é necessário permitir que valores arbitrários sejam agregados às exceções disparadas.

É comum encontrarmos implementações de tratamento de exceções em C que utilizem pilhas como variáveis globais para fazer o tratamento de exceções.

Esse modelo, deveria, idealmente, permitir que utilizemos outros tipos além de inteiros para associarmos com as exceções disparadas e permitir que exceções não capturadas fossem tratadas por funções específicas, assim como ocorre em C++. Porém, para garantir que só há uma variável global e que todos a usem, deve-se utilizar a palavra reservada extern da linguagem C para declará-la sem definí-la e definí-la em um um único local em todo o código.

Isso requer um esforço extra para o programador, mas pode ser melhorado com o uso de bibliotecas estáticas ou dinâmicas, que é um esforço com o qual grande parte dos programadores de C já está acostumado. Contudo, uma grande limitação de tal modelo persiste, que é o a inadequação de seu uso em programação paralela, além de tornar a implementação demasiadamente complexa.

Na implementação descrita nesse texto, foi utilizada a passagem de ponteiros através de argumentos para permitir que funções, ao disparar exceções, retornem valores arbitrários associados com a exceção.

int divide(int a, int b, jmp_buf catch, void **exception)
{
  if (b) {
    return a / b;
  } else {
    // throw(const char *)
    *exception = "Divisão por 0 impossível no conjunto dos números inteiros";
    longjmp(catch, 1);
  }
}

int main()
{
  jmp_buf try;
  char *exception = NULL;
  //try
  if (!setjmp(try)) {
    c = divide(a, b, try, &exception);
  // catch(char *)
  } else if (exception)
    printf("%s\n", exception);
  // catch(...);
  } else {
    printf("Exceção desconhecida, impossível de tratar\n");
    exit(EXIT_FAILURE);
  }
}

Utilizar simples ponteiros vazios, no entanto, nos impede de identificar quem disparou a exceção, o que torna inviável seu uso em problemas mais complexos, que é justamente onde o tratamento de exceções é mais adequado. Para contornar esse problema, programadores provavelmente utilizariam extensões, que, como consequência, quebraria a interoperabilidade do protocolo, novamente tornando seu uso inviável.

Para efetivamente resolver o problema, precisamos identificar os tipos em tempo de execução, assim como ocorre em C++. Há muitas abordagens para identificar tipos em tempo de execução. Uma abordagem que garante desempenho, é associar um valor inteiro a um tipo, porém novamente teria-se o problema de evitar colisões. Uma abordagem que parece mais atrativa, é usar uma string que contenha a assinatura do tipo para identificar o mesmo. Utilizando essa abordagem, sacrificamos um pouco de desempenho e ganhamos um recurso mais flexível para o trabalho.

struct _gvalue
{
  const char *name;
  void *value;
};

int divide(int a, int b, jmp_buf catch, struct _gvalue *exception)
{
  if (b) {
    return a / b;
  } else {
    // throw(const char *)
    exception->name = "const char *";
    exception->value = "Divisão por 0 impossível no conjunto dos números inteiros";
    longjmp(catch, 1);
  }
}

int main()
{
  jmp_buf try;
  struct _gvalue exception = {NULL, NULL};
  //try
  if (!setjmp(try)) {
    c = divide(a, b, try, &exception);
  // catch(char *)
  } else if (!strcmp(exception.name, "const char *"))
    printf("%s\n", exception);
  // catch(...);
  } else {
    printf("Exceção desconhecida, impossível de tratar\n");
    exit(EXIT_FAILURE);
  }
}

Uma das limitações desse modelo é que a busca por um bloco que trate exceções que sejam compatíveis com a disparada ocorre verificando a assinatura do tipo, e não o tipo em si. Dessa forma, conversões entre tipos não irão funcionar implicitamente, o que não é tão ruim, já que a proposta da linguagem é ser uma linguagem de médio nível.

Também, typedefs de tipos e assinaturas que usem convenções de nomes diferentes ("char *"/"char*", ...) serão reconhecidos como tipos novos, não sinônimos para tipos existentes, quebrando a consistência da linguagem. A solução que proponho é evitar o uso de typedefs ao disparar exceções e colocar um comentário nas funções que disparem exceções listando as assinaturas dos tipos que podem ser disparados com tais exceções.

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

GNU Emacs, o primeiro GNU

A história do hardware

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

Mupen64plus, o melhor emulador de Nintendo 64 disponível para GNU/Linux

Entendendo os codecs, os containers formats e por que o Ogg é tão bom

Leitura recomendada

Ponteiros - Saindo de Pesadelos

lib cURL - Trabalhe com URLs em C

Projeto Icecream (parte 1)

Cuidado com números em Ponto Flutuante

Algum humor e C++ Design Patterns (parte 2)

  
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




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts