Miscelâneas sobre a linguagem C
Este artigo descreve alguns pontos pouco comuns sobre a linguagem C e que muitas vezes não são oferecidos ao programador iniciante da linguagem.
Sobre a função de biblioteca free()
Segundo o padrão ANSI C, é seguramente válido passar um ponteiro
NULL a função free(), portando o trecho de código
abaixo não apresenta nenhum erro de execução:
char *buffer = NULL; free(buffer);
Entretanto, chamar mais de uma vez a função free() em um mesmo ponteiro (double free) é um erro, ou pelo menos é uma
condição anormal que é devidamente sinalizada pelo sistema operacional. Por exemplo, o código abaixo:
char *buffer = malloc(BUFSIZ); free(buffer); free(buffer);
Apresenta a seguinte mensagem de erro no Mac OS X:
doublefree(3744) malloc: *** error for object 0x800000: double free *** set a breakpoint in malloc_error_break to debug
No Linux a mensagem de erro também segue um backtrace e um dump de memória, o que facilita em achar aonde está o problema.
*** glibc detected *** ./free: double free or corruption (top): 0x0804a008 ***
Por último, a região de memória que se passa a função free() deve ter sido previamente criada através da função malloc() ou relacionada. O trecho de código abaixo:
char buf[1024]; free(buf);
Exibe a seguinte mensagem de erro no Mac OS X:
notallocated(48169) malloc: *** error for object 0xbffff6bc: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug
Já no Linux ocorre uma falha de segmentação (Segmentation fault).
Sobre o operador sizeof
O operador sizeof aplicado a um vetor retorna o número de
elementos desse vetor, multiplicado pelo tamanho (em bytes) do tipo base desse
elemento. Nos exemplos abaixos:
char buffer[80];
char *boolean[] = {"True","False",NULL};
O número de elementos de buffer é 80, pois a expressão
sizeof(buffer)/sizeof(char) ou 80/1 é igual a 80. O número de
elementos de boolean é 3, conforme o resultado da expressão
sizeof(boolean)/sizeof(char*) ou 12/4 = 3.
#define NELEM(v) (sizeof(v)/sizeof(v[0]))
O operador sizeof pode ser utilizado para retornar o
tamanho de uma string em um vetor.
char str[] = "Hello World!"; int tam = sizeof(str); /* tam = 13 (12+1 do \0) */
Cuidado que a dica só funciona para vetores, quando se usa em ponteiros o resultado não vai ser o que se espera, o tamanho vai ser igual ao tamanho de um ponteiro!
char *s = "Hello World!"; int tam = sizeof(s); /* tam = 4 pois é um ponteiro! */
Sobre ponteiros & vetores
Não se pode levar ao pé da letra que um ponteiro em C é estritamente
igual a um vetor do mesmo tipo. Um ponteiro char *p pode fazer
referência a um vetor char buffer[], porém uma declaração de
ponteiro nunca está associado a criação de conteúdo, enquanto que um vetor
está associado a criação do conteúdo.
char octal[8] = {'0','1','2','3','4','5','6','7'}; /* contem */
char *p = octal; /* aponta */
Lembrando que o nome do vetor é uma constante e que octal é
um endereço equivalente à expressão &octal[0]. O valor da
expressão *p é o caracter 0, o valor de *(p+2) é
o caracter 2, o valor de p[7] é o caracter 7 e o valor de
*(p++) é o caracter 1. A regra para ponteiro e vetores
*(p+n) == p[n] == octal[n] é sempre válida.
Sobre ponteiros de função
Um dos tópicos que as vezes parece magia negra na linguagem C é a
declaração de ponteiros de função. De forma simples, um ponteiro de função
é declarado com um asterisco * como prefixo do nome da função,
por motivos de precedência entre operadores, deve-se utilizar parênteses
para delimitar o nome do ponteiro, por exemplo (*nome).
| Declaração de função | Declaração de ponteiro de função |
|---|---|
void sync(void); |
void (*ptr_sync)(void) = sync; |
void exit(int); |
void (*ptr_exit)(int) = exit; |
size_t strlen(char *); |
size_t (*ptr_strlen)(char *) = strlen; |
char *strstr(char *, char *); |
char* (*ptr_strstr)(char *, char*) = strstr; |
Com estas definições, é possível evocar as funções originais através de
ponteiros, como em ptr_sync() ou ptr_exit(1) e o
mesmo para as demais funções.
Sobre função de biblioteca qsort()
A função qsort permite ordenar um vetor e possui o seguinte protótipo de chamada no Mac OS X:
qsort(void *base,
size_t nel,
size_t width,
int (*compar)(const void *, const void *));
Aonde base é o endereço base do vetor (o nome do vetor), nel é o número de elementos, width é o tamanho de um elemento e compar é uma função de comparação que retorna os inteiros -1, 0 ou +1 conforme a expressão “A-B”.
Exemplo de implementação para a função compar que utiliza comparação de strings.
int scmp(const void *p1, const void *p2)
{
const char *s1 = *(char **)p1;
const char *s2 = *(char **)p2;
return strcmp(s1, s2);
}
Vamos classificar o vertor char *v[] = {"John", "Paul", "George", "Ringo"} com qsort e a função scmp.
qsort(v, sizeof(v)/sizeof(v[0]), sizeof(v[0]), scmp);
A função qsort modifica o vetor v, de forma que o resultado de qsort é o vetor ordenado conforme a regra de compar.
Sobre o modelo ILP32
O modelo de dados ILP32 estabelece para os tipos inteiros (int), inteiros longos (long) e ponteiros (void *) que ambos possuem um tamanho fixo de 32 bits, conforme a sigla indica.
É considerado seguro guardar um inteiro em um pointeiro e vice-versa. São exemplos de sistemas que utilizam o modelo ILP32, o Mac OS X (em 32 bits) e o Linux (em 32 bits).
Para forçar uma compilação em 32 bits, deve-se passar a opção -m32 ao compilador GCC.
Sobre o modelo LP64
Uma boa parte dos sistemas Unix de 64 bits, como o Linux e o Mac OS X,
adotam o modelo de programação LP64. Neste modelo, os inteiros (int) possuem
32 bits e os inteiros longos (long) e ponteiros (void*) possuem 64 bits,
conforme a sigla indica.
Guardar um ponteiro em um tipo inteiro em um ambiente de 64 bits é dar um tiro no pé, e o compilador vai gerar um aviso, mas é considerado seguro guardar um ponteiro em um inteiro longo.
Para forçar uma compilação em 64 bits, deve-se passar a opção -m64 ao compilador GCC.