Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

_Isis_

[Tutorial] Exibindo Erros

Recommended Posts

Original: http://strufts.blogspot.com

 

Linguagens como C++, Java, C#, Python e Ruby possuem um mecanismo de tratamento de erros que envolve exceções. Adicionalmente, usa-se algum método para imprimir um stack trace, que informa onde ocorreu o erro. Em C, tudo deve ser resolvido com ifs e o método mais comum é avaliar o valor de retorno da função e imprimir uma mensagem dizendo que tal operação deu errado. Exemplo: se a tentativa de conectar a um servidor que suporta no máximo 1 cliente deu errado, imprimimos a mensagem "Servidor lotado.". O problema dessa abordagem é que temos retrabalho na maioria das vezes, já que as funções, quando não retornam somente um valor documentado indicando sucesso ou erro, atribuem um determinado valor a uma variável.

 

Este "tutorial" visa mostrar outra abordagem da exibição de erros, ou, se você preferir, tratamento de erros.

 

 

A VARIÁVEL ERRNO

 

Comecemos com um código simples de leitura da entrada padrão.

 

#include <stdio.h>
  #include <errno.h>
  int main(int argc, char const* argv[])
  {
   int n;
   do {
	   scanf("%d",&n);
   }while(!feof(stdin));
   printf("%m\n");
   return 0;
  }

 

Esse código serve apenas para demonstrar um formatador do printf que pode ser usado para enviar mensagens ao usuário. Do manual da função:

 

m (Glibc extension.) Print output of strerror(errno).No argument is required.

 

Vemos que o formatador %m é uma extensão da Glibc. Se você está no Windows, deve utilizar a função strerror (C99), aqui definida em string.h, desse modo:

 

#include <stdio.h>
  #include <errno.h>
  #include <string.h>
  int main(int argc, char const* argv[])
  {
   int n;
   do {
	   scanf("%d",&n);
   }while(!feof(stdin));
   printf("%s\n",strerror(errno));
   return 0;
  }

 

Atente para o seguinte: a função strerror também pode atribuir um valor à variável errno nestas duas situações:

 

1-O valor passado como argumento é inválido.

2-Espaço insuficiente para armazenar a mensagem de erro.

 

#include <stdio.h>
  #include <errno.h>
  #include <string.h>
  int main(int argc, char const* argv[])
  {
   printf("%s\n",strerror(-123));
   return 0;
  }

 

Várias funções utilizam a variável errno para "comunicar" o resultado de uma operação. Mas atenção: NÃO DECLARE A VARIÁVEL ERRNO EM SEU PROGRAMA. O ISO C a define como um lvalue inteiro e que não deve ser explicitamente declarada.

 

A variável é local a threads, ou seja, quando é definida por uma thread não afeta seu valor em outra thread. Portanto, ao invés de encher de printf("Erro aqui") ou print("%s\n",ERRO1), prefira o uso dessa variável.

 

Se você está em um ambiente UNIX, para visualizar as mensagens de erro disponíveis pode executar o seguinte código.

 

#include <stdio.h>
  #include <errno.h>
  int main(int argc, char const* argv[])
  {
   printf("Valor de sys_nerr: %d\n",sys_nerr);
   int i;
   for(i=0;i<sys_nerr;i++)
	   printf("%s\n",sys_errlist[i]);
   return 0;
  }

 

Podemos ignorar os warnings sobre o uso de sys_errlist, já que queremos somente verificar a quantidade de erros cadastrada. Como o compilador indica, o uso explícito de sys_errlist deve ser evitado. Logo, a função perror deve ser utilizada somente para erros definidos pelo usuários (em defines, por exemplo), apesar de pertencer ao C99.

 

 

EXTENSÕES GLIBC - ERROR.H

 

Em Java temos a estrutura try-catch-finally para tratamento de exceções, sendo que o catch é utilizado, geralmente, para imprimir um stack trace do programa, contendo informações sobre o erro, tais como número de linha e nome da função.

 

Se você está num ambiente Linux e seu programa não vai ser portável, pode utilizar o header error.h, contendo 2 funções: error e error_at_line.

 

A função error aceita, no mínimo, 3 argumentos:

  • o primeiro é um inteiro e, se diferente de zero, especifica o valor retornado pelo programa ao chamar a função exit (não é necessário que você chame a função exit explicitamente, pois error já faz isso se este argumento for diferente de zero)
  • o segundo argumento é um inteiro que representa o valor do erro (aleatório existente em sys_errlist ou a variável errno)
  • o terceiro argumento pode assumir duas formas: uma string (nesse caso a função fica restrita a 3 argumentos) ou um especificador de formato, igual ao do printf, seguido dos dados a imprimir.

O que é impresso na tela depende do valor do segundo argumento e o comportamento do programa depende do valor do primeiro argumento. Quadro resumo:

 

(STATUS-1º argumento, ERRNUM-2º argumento)

 

(0;0) : Imprime o nome do programa seguido por ": " e a mensagem especificada no terceiro argumento, continuando a execução.

 

(0;Diferente de 0) : Imprime o nome do programa seguido de ": ", da mensagem especificada no 3º argumento, mais ": " e o resultado de perror(errnum) (2º argumento), continuando a execução.

 

(Diferente de 0;0) : Imprime o nome do programa seguido por ": " e a mensagem especificada no terceiro argumento, terminando a execução e retornando o valor do 1º argumento.

 

(Diferente de 0; Diferente de 0) : Imprime o nome do programa seguido de ": ", da mensagem especificada no 3º argumento, mais ": " e o resultado de perror(errnum) (2º argumento), terminando a execução e retornando o valor do 1º argumento.

 

A segunda função, error_at_line, tem comportamento semelhante ao da função error, mas com a inclusão do nome do arquivo e do número da linha. Seus argumentos são:

 

1- int status : valor de retorno do programa ao sair. A função exit só é chamada se status != 0.

2- int errnum : valor do erro (errno).

3- const char * filename : nome do arquivo.

4- unsigned int linenum : número da linha.

5- const char *format : igual ao 3º argumento da função error.

 

Para o terceiro e quarto argumentos é bom utilizar os valores do pré-processador __FILE__ e __LINE__ quando estamos tratando dos erros do próprio programa, mas outros valores podem ser utilizados em casos como parsing de um arquivo inválido.

 

#include <stdio.h>
  #include <errno.h>
  int main(int argc, char const* argv[])
  {
   int n;
   do {
	   scanf("%d",&n);
   }while(!feof(stdin));
   error_at_line(0,errno,__FILE__,__LINE__,"%s %d","TESTE DE MENSAGEM",errno);
   puts("LINHA A MAIS");
   return 0;
  }

 

 

EXTENSÕES GNU - BACKTRACE

 

Eu acho sacal debugar um programa com o GDB (não uso Nemiver e nem DDD) e gosto da história do printStackTrace do Java (apesar de não ir muito com o tamanho da API). Como o único lugar onde programo em C é Linux, posso me dar ao luxo de utilizar o header execinfo. Nele temos 3 funções para ter um backtrace do programa.

 

A função backtrace aceita dois argumentos: um void** para um buffer onde será colocado o backtrace e um int que especifica a quantidade máxima de endereços void * armazenada no buffer.

 

#include <stdio.h>
  #include <execinfo.h>

  void trace()
  {
   #define TAM 100
   void *buffer[TAM];

   int total_pointer = backtrace(buffer,TAM);
   int i;
   for(i=0;i<total_pointer;i++)
	   printf("%x\n",buffer[i]);

  }

  void soma(int n)
  {
   if (n)
	   soma(n-1);
   else
	   trace();
  }
  int main(int argc, char const* argv[])
  {
   soma(3);
   return 0;
  }

 

A backtrace armazena endereços no buffer passado como argumento. Para que seja possível entender algo precisamos usar a função backtrace_symbols, passando o buffer da função backtrace como primeiro argumento e um inteiro que especifica a quantidade de endereços nesse buffer. Esse valor é retornado pela função backtrace.

 

#include <stdio.h>
  #include <execinfo.h>
  #include <stdlib.h>
  void trace()
  {
   #define TAM 100
   void *buffer[TAM];

   int total_pointer = backtrace(buffer,TAM);
   char ** strings = backtrace_symbols(buffer,total_pointer);
   int i;
   for(i=0;i<total_pointer;i++)
	   printf("%s\n",strings[i]);
   free(strings);
   strings = NULL;
  }

  void soma(int n)
  {
   if (n)
	   soma(n-1);
   else
	   trace();
  }
  int main(int argc, char const* argv[])
  {
   soma(3);
   return 0;
  }

 

Note a ausência de malloc para a variável char ** strings. A função backtrace_symbols aloca o espaço, sendo que o programador deve dar free depois de usar o array de strings.

 

Para não precisar de malloc e nem de free, podemos usar a função backtrace_symbols_fd, que escreve o backtrace em um descritor de arquivo.

 

#include <stdio.h>
  #include <execinfo.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <string.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  void trace()
  {
   #define TAM 100
   void *buffer[TAM];

   int total_pointer = backtrace(buffer,TAM);
   int fd = open("trace.txt",O_RDWR|O_CREAT);
   if (fd == -1) {
	   printf("%s\n",strerror(errno));
	   exit(1);
   } else
	   backtrace_symbols_fd(buffer,total_pointer,fd);
  }

  void soma(int n)
  {
   if (n)
	   soma(n-1);
   else
	   trace();
  }
  int main(int argc, char const* argv[])
  {
   soma(3);
   return 0;
  }

 

 

d709a.png

 

 

DEBUGANDO O MALLOC DENTRO DO PROGRAMA

 

Outras funções próprias do GNU, definidas no header mcheck. Podemos utilizar um arquivo para fazer o dump das chamadas do malloc, mas temos que exportar a variável MALLOC_TRACE.

 

#include <mcheck.h>
  #include <stdio.h>
  #include <stdlib.h>
  int main(int argc, char const* argv[])
  {
   mtrace();
   char *s=(char*)malloc(1);
   free(s);
   muntrace();
   return 0;
  }

 

0eae1.png

 

Note que não é user-friendly. Para saber o que se passa usamos o comando mtrace. Nesse caso, vemos que não liberamos memória em algum ponto do programa. Quando liberamos toda a memória alocada, a mensagem "No memory leaks" é exibida.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.