You are currently browsing the category archive for the 'C' category.
Palavras-chave: C, pré-processador, macros, macro, define, gcc
O pré-processador do GCC define uma série de macros por default. Muitas delas podem lhe ser útil, caso se deseje que um determinado bloco de código seja compilado para apenas uma determinada arquitetura, por exemplo. Para conhecer estas macros, basta perguntar ao pré-processador:
$ echo foo |gcc -dM -E - (...) #define __VERSION__ "4.1.2 20061115 (prerelease) (Debian 4.1.1-21)" #define i386 1 #define __linux__ 1 #define __gnu_linux__ 1 #define __i486__ 1 #define unix 1 #define __i386__ 1 #define __SIZE_TYPE__ unsigned int #define __ELF__ 1 #define __FLT_HAS_QUIET_NAN__ 1 #define __FLT_MAX_10_EXP__ 38 #define __LONG_MAX__ 2147483647L #define __FLT_HAS_INFINITY__ 1 #define linux 1 (...) $
A saída acima, mostra algumas das macros definidas pelo pré-processador i386-linux do Debian Etch. No pré-processador para a arquitetura arm-palmos temos as seguintes macros:
$ echo foo |arm-palmos-gcc -dM -E - (...) #define __palmos__ 1 #define __FLT_MIN__ 1.17549435e-38F #define __CHAR_BIT__ 8 #define __WCHAR_MAX__ 2147483647 #define __DBL_DENORM_MIN__ 4.9406564584124654e-324 #define __FLT_EVAL_METHOD__ 0 #define __SIZE_TYPE__ long unsigned int #define __ELF__ 1 #define __DBL_MIN_10_EXP__ (-307) #define __FINITE_MATH_ONLY__ 0 #define __ARMEL__ 1 #define __GNUC_PATCHLEVEL__ 1 #define __FLT_RADIX__ 2 (...) $
Palavras-chave: stdout, stderr, verbose, terminal, console, /dev/null Uma forma de impedir que seu programa imprima na tela, seja por linkar com uma biblioteca que abusa da verbosidade ou para garantir que um daemon não polua seu terminal quando for para background, é fechando a saída padrão (stdout) e a saída de erro (stderr), como mostrado no exemplo seguinte:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int fd = open("/dev/null", O_WRONLY);
close(1); // stdout
close(2); // stderr
dup(fd); // Duplica o descritor fd que corresponde a /dev/null
dup(fd); // usando o menor numero disponivel, no caso, 1 e 2
printf("No verbosity anymore!\\n"); // Nao imprime nada na tela
return 0;
}
Apenas fechar as saídas não é o bastante, senão um printf ou um cout (C++), que por padrão escrevem em stdout, iriam escrever em um descritor inválido. Direcionar a saída para o /dev/null é uma boa opção, já que ele descarta tudo que é escrito nele. Vale observar que a implementação mostrada nesta dica não é thread-safe.
Palavras-chave: warning, unused parameter, variable
Em alguns casos temos que implementar uma função (na maioria dos casos uma callback) cujo o protótipo já é definido pela API utilizada. Alguns parâmetros podem ser desnecessários, mas geram warnings de compilação caso não forem utilizados. O exemplo a seguir gera um warning quando compilado com o gcc:
void my_callback(char *data, int size)
{
printf("Size is %d\n", size);
return;
}
foo.c:2: warning: unused parameter 'data'
Podemos contornar esta situação de duas formas. A primeira e mais portável é fazer um cast para o tipo void:
void my_callback(char *data, int size)
{
(void) data;
printf("Size is %d\n", size);
return;
}
A segunda forma é usar uma extensão do gcc:
void my_callback(char *data __attribute__((unused)), int size)
{
printf("Size is %d\n", size);
return;
}
Palavras-chave: non-constant initializers, dynamic stack allocation
Muitas vezes alocamos memória para um buffer baseado em um tamanho que é passado como parâmetro para a função. Como no seguinte exemplo:
void foobar (size_t size)
{
char *buffer = malloc(size * 2);
No gcc existe uma extensão pouco conhecida, mas bastante útil, chamada non-constant initializers. Usando este recurso, a função poderia ser simplificada para:
void foobar (size_t size)
{
char buffer[size * 2];
A variável é alocada na pilha com um tamanho dinâmico que depende do valor passado como parâmetro e a desalocação do buffer é feita no retorno da função.
Palavras-chave: C, C++, varargs, argumentos variáveis
Para escrever funções que aceitam quantidade variável de parâmetros, usa-se o mecanismo de stdargs do C. Para isso coloca-se “…” como último parâmetro na declaração da função e depois usa-se as funções de stdarg.h para acessar os valores passados quando a funcão é chamada.
-
É obrigatório a inclusão de pelo menos um parâmetro normal antes da parte variável. (ex.:
char *concat(const char *s, …)) -
Qualquer tipo de valor pode ser passado como parâmetro, mas é impossível determinar em runtime o tipo dos valores que uma função recebeu. Porém é possível criar formas de o código que chama a funcão comunicar os tipos dos valores passados. Por exemplo, a função printf() determina os tipos dos parâmetros recebidos pela string de formatação (%s para char*, %i para int etc); a função
concat()no exemplo espera que todos os parâmetros recebidos sejam strings e assim por diante. -
O número de parâmetros recebido também não pode ser determinado explicitamente. É necessário usar artifícios adicionais como passar uma variável extra com o número de valores sendo passados, usar o valor NULL como indicador de último parâmetro ou derivar a quantidade de valores a partir da string de formatação, como no caso do printf() e similares.
Exemplo:
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
char *concat(const char *s, ...)
{
va_list args;
char *tmp;
char *res;
size_t len= strlen(s);
// pega um handle ao início da lista de parâmetros
va_start(args, s);
// calcula o tamanho total de todas as strings
// pega o próximo parâmetro da lista, até chegar no NULL
while ((tmp= va_arg(args, char*))) {
len+= strlen(tmp);
}
va_end(args);
res= malloc(len+1);
if (!res) return NULL;
// cria a string concatenada
strcpy(res, s);
va_start(args, s);
// pega o próximo parâmetro da lista, até chegar no NULL
while ((tmp= va_arg(args, char*))) {
strcat(res, tmp);
}
va_end(args);
return res;
}
int main()
{
char *s= concat("hello", " ", "world", "!!!!", NULL);
puts(s);
free(s);
return 0;
}
Se você quer usar macros com número de parâmetros variável (por exemplo, uma macro que chama uma função com stdargs), veja o post sobre o assunto.
Palavras-chave: C, arquivo texto, linhas, leitura, getline
Como dito num post anterior, a glibc possui uma função para ler linhas de comprimento arbitrário de um arquivo texto. Mas como esta função é especifica a glibc, alguns leitores pediram uma versão que faça o mesmo em qualquer sistema. Uma das muitas formas de fazer isto é:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *pegalinha(FILE *f, char **buffer, size_t *buflen)
{
char *ptr;
char *tmp;
int c;
if (!*buffer || !*buflen)
{
*buflen= 32;
*buffer= (char*)malloc(*buflen);
if (!*buffer)
return NULL;
}
ptr= *buffer;
while ((c= fgetc(f)) != EOF) {
*ptr++= c;
if (c == '\\n') break;
if (ptr - *buffer >= *buflen-1)
{
// aumenta o buffer um pouco mais
tmp= (char*)realloc(*buffer, *buflen+32);
if (!tmp)
return NULL;
// atualiza ptr para o caso do novo buffer estar em outro lugar
ptr= tmp + (ptr - *buffer);
*buflen+= 32;
*buffer= tmp;
}
}
*ptr= 0;
return c==EOF ? NULL : *buffer;
}
int main(int argc, char **argv)
{
FILE *file;
char *linha= NULL;
size_t tamlinha= 0;
if (argc != 2) { printf("%s \\n", argv[0]); exit(1); }
if (!(file= fopen(argv[1], "r"))) { perror(argv[1]); exit(1); }
while (pegalinha(file, &linha, &tamlinha))
{
printf("%i:%s", tamlinha, linha);
}
if (linha)
free(linha);
fclose(file);
return 0;
}
Palavras-chave: C, stdin, stdout, descritor, descriptor, inode, comparar, compare
Ao escrever filtros (programas que processam dados provenientes da entrada padrão e os escrevem na saída padrão) pode-se querer evitar que o arquivo de entrada seja o mesmo arquivo utilizado para saída. Um exemplo de um programa assim seria um conversor de formato: o que fazer caso o filtro seja invocado como
$ gif2png < tempfile > tempfile
ou ainda
$ sig2uns tempfile > tempfile
potencialmente corrompendo dados quando eles são lidos e processados em blocos?
Uma solução simples em sistemas que utilizam sistemas de arquivo similares aos do UNIX é comparar o número do inode e o dispositivo dos descritores de entrada e saída, sejam eles arquivos ou stdin/stdout. Isso pode ser implementado em C como:
fstat(ifd, &st);
st.st_mode &= S_IFMT;
if (st.st_mode != S_IFCHR && st.st_mode != S_IFBLK) {
dev = st.st_dev;
ino = st.st_ino;
}
fstat(ofd, &st);
st.st_mode &= S_IFMT;
if (st.st_dev == dev && st.st_ino == ino) {
/* entrada e saída são iguais */
return 1;
}
Isso é exatamente o que o programa “cat” faz. Se você experimentar fazer cat de um arquivo para ele mesmo, o resultado é:
$ cat a.out > a.out
cat: a.out: input file is output file
Finalmente, note que isso não vale para dispositivos de caractere ou bloco, como tratado no trecho de código acima.
Palavras-chave: C, linkagem estática, undefined reference, referência indefinida
Ao linkar programas que usam bibliotecas estáticas que por usa vez usam outras bibliotecas estáticas, pode ocorrer de o linker reclamar da falta de símbolos de uma das bibliotecas, apesar de eles estarem comprovadamente definidos em uma delas. Isso pode ocorrer por causa da ordem errada em que as bibliotecas foram passadas ao linker.
Apesar de o linker se virar para encontrar símbolos em bibliotecas dinâmicas, para as estáticas é necessário passá-las em ordem de dependência. Por exemplo, se seu programa usa a libpng.a que usa a libz.a internamente, o comando correto para linká-lo é:
gcc programa.c -oprograma /usr/lib/libpng.a /usr/lib/libz.a
Mas se a ordem estivesse invertida:
$ gcc programa.c -oprograma /usr/lib/libz.a /usr/lib/libpng.a /usr/lib/libpng.a(png.o): In function `png_reset_crc': undefined reference to `crc32' /usr/lib/libpng.a(png.o): In function `png_calculate_crc': undefined reference to `crc32' /usr/lib/libpng.a(png.o): In function `png_calculate_crc': undefined reference to `crc32' /usr/lib/libpng.a(png.o): In function `png_reset_zstream': undefined reference to `inflateReset' collect2: ld returned 1 exit status
Palavras-chave: C, macro, multi-statement, cpp, pré-processador, preprocessor
Com alguma freqüência é necessário de definir macros com múltiplos comandos (statements) que possam ser usados de forma sintaticamente equivalente a chamadas de função. Nesse caso, não basta definir uma seqüência como:
#define foo(x) a1(x); a2(x)
Isso seria desastroso após um if, por exemplo. Tampouco adiantaria delimitar o bloco com chaves, o que não funcionaria numa situação como:
if (y) foo(z); else return;
Como, então, podemos definir tais macros? A resposta é: utilizando expressões “do {…} while(0)”, cuja utilidade em outras situações é bastante duvidosa. Veja o exemplo:
#define foo(x) do { a1(x); a2(x); } while (0)
Definindo macros dessa forma, cumprimos nosso objetivo: apesar da declaração aparentemente estranha, seu uso é equivalente a uma chamada de função em qualquer caso, incluindo loops e condicionais.
Palavras-chave: C, gcc, cpp, macros, …, varargs, número de parâmetros variável, variadic macros
Para escrever macros de função com número variável de argumentos:
#define DPRINT(fmt, ...) printf("DEBUG: "fmt"\\n", ## __VA_ARGS__)
__VA_ARGS__ será substituído pelos parâmetros dados. E o ## entre a vírgula e __VA_ARGS__ se encarregará de remover a vírgula quando o número de argumentos for 0.
Exemplo:
#include <stdio.h>
#define DPRINT(fmt, ...) printf("DEBUG: "fmt"\\n", ##__VA_ARGS__)
int main()
{
int i= 0, j= 0;
DPRINT("chegou aqui");
DPRINT("valor de i = %i, j = %i", i, j);
return 0;
}
Nota: O Visual C++, a partir da versão 2005, passou a incluir o mesmo recurso.
Palavras-chave: C, sprintf, snprintf, asprintf, formatando strings com tamanho variável
Inspirado pelo post do Kojima eu resolvi postar uma dica para um problema similar: normalmente precisamos formatar algum texto usando a função sprintf(), porém ela requer um buffer pré-alocado.
Bem, usar sprintf() é loucura, pois buffer-overflows podem acontecer. Usamos, então, snprintf() que limita o tamanho do resultado. Mas mesmo assim podemos ficar insatisfeitos, pois ele pode sair truncado.
Porém o pessoal do GNU criou a função asprintf() que calcula e aloca a memória necessária para que tudo caiba perfeitamente, incluindo o terminador final. O uso é bem simples:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char *s;
if (asprintf(&s, "argc=%d", argc) > -1) {
printf("s=\"%s\"\\n", s);
free(s);
}
return 0;
}
Lembre-se de definir _GNU_SOURCE, pois esta é uma extensão ao padrão ANSI C e POSIX. Segundo a man-page, também está disponível em sistemas BSD, vale a pena conferir.
Palavras-chave: C, descritor descriptor stdout redireção redirection
É comum em utilitários que rodam em um terminal e escrevem na saída padrão (stdout) a existência de um parâmetro que permite redirecionar a saída para um arquivo (geralmente -o).
Uma maneira simples de implementar essa redireção é copiando o descritor do arquivo especificado para o descritor 1 (do stdout). Pode-se implementar isso da seguinte maneira, dentro do tratamento dos parâmetros de linha de comando:
case 'o':
ofd = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (ofd < 0) {
perror("open");
exit(1);
}
dup2(ofd, STDOUT_FILENO);
break;
onde optarg é o parâmetro passado para -o. Assim, o restante do código pode ser escrito sem se preocupar com o arquivo de saída, usando printf() normalmente para escrever em stdout.
Palavras-chave: C, memória, otimização, arrays, vetores, bitmap, pixmap
Ao se escrever código para manipulação de strings ou imagens, é comum percorrer grandes vetores de bytes. A maneira óbvia de fazer isto é algo no estilo de:
char *ptr= buffer;
for (i= 0; i < fim; i++)
*ptr++= 0;
Mas percorrer memória byte a byte é bastante lento, por questões de alinhamento de memória, o fato dela ser endereçada por palavra. Por exemplo, um int em i386 e long em x86_64.
O exemplo abaixo inclui código que percorre por byte e por int, mas pode ser facilmente modificado para percorrer por longs:
#include <sys/types.h>
int main()
{
char buffer[1000];
uint32_t *iptr;
char *ptr;
int i, j;
int max= 973;
for (j= 0; j < 100000; j++)
{
#ifdef lento
ptr= buffer;
for (i= max-1; i>=0; --i)
*ptr++= 0;
#else
iptr= (uint32_t*)buffer;
// processa o vetor de 4 em 4 bytes,
// arredondando o número de elementos
for (i= max>>2-1; i>=0; --i)
*iptr++= 0;
ptr= iptr;
// processa os últimos bytes restantes
switch (max&((1<<2)-1))
{
case 3: *ptr++= 0; // sem break
case 2: *ptr++= 0; // sem break
case 1: *ptr++= 0;
break;
}
#endif
}
return 0;
}
Os tempos de execução para o código com percorrimento por palavras de 8, 32 e 64bits em uma máquina x86_64, são de: 0.190s, 0.100s e 0.025s, uma melhora de até 800%(!).
Obviamente, neste caso, um memset() seria mais rápido mas o ponto é que este conceito pode ser estendido para casos em que o memset() não serve. Ainda há espaço para mais otimizações, mas isto é deixado como exercício ;)
Obs.: lembre-se que o comprimento de long varia de acordo com a arquitetura e é prudente colocar trechos que dependem disso dentro de #ifdefs.
Leia também um artigo relacionado, sobre o aproveitamento da memória cache nesta dica do Eduardo.
Palavras-chave: OpenGL, Linux, multi-thread, drivers NVidia, crash, segmentation fault, segfault
Se seu programa explode em uma função OpenGL sem nenhum motivo aparente, verifique se a chamada está sendo feita desde a thread onde a OpenGL foi inicializada.
As bibliotecas que vêm com os drivers da NVidia (libGL.so) para X não são muito tolerantes com programas com multi-threading, enquanto a libGL.so da Mesa parece ser mais tolerante a isso. Isso pode levar à confusa situação de o programa rodar sem problemas em alguns sistemas e morrer em outros.
Nos casos em que tive esse problema, o segfault ocorreu na função glViewport(), mas é provável que ocorra com outras funções.
Não existe solucão mágica, mas um workaround — para ao menos testar a hipótese — é mudar o LD_LIBRARY_PATH para apontar ao libGL.so da xorg (se não a tem instalado, tente copiar de outro lugar etc). Verifique se a biblioteca correta está sendo usada com ldd e teste. Já a solucão definitiva é mover as chamadas OpenGL que acessam o mesmo contexto para uma única thread (ou protegê-las com mutexes).
Palavras-chave: C, C++, CPP, macro, string, identificador
Para transformar o nome de um macro ou identificador em string:
#define STR(s) #s
Para transformar o valor de um macro ou identificador em string:
#define STRV(s) STR(s)
O seguinte exemplo ilustra a diferença:
#define STRV(s) STR(s)
#define STR(s) #s
int main()
{
puts("1: "STR(puts));
puts("2: "STR(123));
puts("3: "STR(__LINE__));
puts("4: "STRV(__LINE__));
return 0;
}
Resultado:
1: puts 2: 123 3: __LINE__ 4: 9
Note como STR() retorna um string com o nome do macro, enquanto que STRV() retorna o valor.
Palavras-chave: C, memória, cache, performance, velocidade
Todo processador hoje em dia possui memória cache, que ajuda a melhorar a performance, armazenando os dados mais recentemente acessados, para acesso mais rápido pelo processador.
O que nós freqüentemente ignoramos é que a organização dos dados e o padrão de acesso à memória podem aproveitar muito bem ou simplesmente estragar a ajuda que a cache nos dá.
Vamos comparar duas versões de um programa que soma os elementos de uma matriz, e seus tempos de execução:
/* matriz1 */
#define N 10000
#define M 15000
static int m[N][M];
int main()
{
int i, j, soma = 0;
for (j = 0; j < M; j++) /* coluna a coluna */
for (i = 0; i < N; i++)
soma += m[i][j];
return 0;
}
/* matriz2 */
#define N 10000
#define M 15000
static int m[N][M];
int main()
{
int i, j, soma = 0;
for (i = 0; i < N; i++) /* linha a linha */
for (j = 0; j < M; j++)
soma += m[i][j];
return 0;
}
$ time -p ./matriz1 real 1.44 user 1.38 sys 0.05
$ time -p ./matriz2 real 0.78 user 0.72 sys 0.05
Conseguimos quase cortar pela metade o tempo de execução (de 1,38 para 0,72 segundos), apenas modificando a ordem em que os elementos da matriz são acessados. No segundo caso, os dados são acessados linha a linha (seqüencialmente na memória), aproveitando que blocos de memória contendo elementos vizinhos já foram carregados na memória cache.
Palavras-chave: C, arquivo texto, fgets, linhas de comprimento arbitrário
O método típico para se ler um arquivo texto linha a linha é usando a função fgets() com um buffer estático. O problema é que nem sempre sabemos qual será o comprimento máximo das linhas e, de acordo com a Lei de Murphy, chutar um tamanho “grande o suficiente” pode não ser o suficiente na prática. Além da possibilidade de abusos ou conteúdo malicioso quando o arquivo é fornecido pelo usuário.
A maneira “correta” de se fazer isso é um pouco trabalhosa, mas felizmente a glibc possui a pouca conhecida função getline(). Esta função toma um buffer dinâmico e irá redimensioná-lo se necessário. É uma função específica da glibc, então se seu programa irá rodar em outras plataformas, não a utilize.
Exemplo:
#define _GNU_SOURCE // necessário porque getline() é extensão GNU
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *f= fopen("teste.txt", "r");
size_t len= 100; // valor arbitrário
char *linha= malloc(len);
if (!f)
{
perror("teste.txt");
exit(1);
}
while (getline(&linha, &len, f) > 0)
{
printf("%s", linha);
}
if (linha)
free(linha);
fclose(f);
return 0;
}
É uma pena que não seja uma função portável, mas é algo que pode economizar algumas linhas de código e neurônios quando escrevemos xu^Wprograminhas descartáveis em C.







Comentários Recentes