Simplifique: use Macros (ou não)

- por Sergio Prado

Categorias: Linguagem C Tags: , , ,

Qualquer tecnologia pode ser usada tanto para o bem quanto para o mal. Não é diferente quando falamos de macros. Se bem utilizada, pode facilitar em muito nossa vida, melhorar a leitura do código e até mesmo otimizá-lo. Mas se mal utilizada, podem nos causar uma tremenda dor de cabeça e uma busca interminável por bugs no código.

Mas o que são macros? Analisando de uma forma mais simples, macros são basicamente substituições de strings. Macros são tratadas (expandidas) pelo pré-processador e criadas em linguagem C através da diretiva #define.

Macros são de dois principais tipos:

a. Objeto: normalmente utilizado para dar nome à constantes. Exemplo:

1
#define PI 3.1415926

b. Função: definir uma função (uma forma primitiva de funções inline em C++). Exemplo:

1
#define SOMA(a,b) ((a)+(b))

Seu uso é relativamente simples, mas se não prestarmos atenção à alguns detalhes de como o pré-processador expande as macros, podemos ficar um tempo “batendo cabeça” até descobrirmos o problema. Vamos então apresentar algumas dicas sobre quando devemos ou não usar macros, e em alguns pontos que precisamos prestar atenção.

DICA 1: Usar sempre maiúsculas ao definir macros, seja ela um objeto ou uma função. Este padrão facilita a leitura e manutenção do código.

DICA 2: Evite os famosos números mágicos no código. Essa orientação é antiga, mas muitos ainda cometem este erro. Por exemplo, você tem um buffer de comunicação de 256 bytes, e  em todo ponto onde você acessa esse buffer, você usa o “famoso” número mágico 256. Exemplo:

1
2
3
4
5
6
    char buf[256];
    ...
    bzero(buf, 256);
    ...
    for (i = 0; i < 256; i++) {
        buf[i] = i;

Então você precisa alterar o buffer de comunicação para 512, e sai buscando este número mágico em todo o seu código de mais de 10.000 linhas. Tudo poderia ter sido muito mais fácil se você tivesse definido uma macro e usado ela:

1
#define TAM_BUF 256

DICA 3: Não usar macros para definição de tipos

OK, a intenção é boa, já que essa técnica torna o código mais portável, porém a execução não é das melhores, especialmente quanto tratamos com variáveis tipo ponteiro. Imagine uma macro de um ponteiro para inteiro:

1
2
#define PINT int*
PINT p, q;

Lembre-se de que a macro é apenas uma substituição de strings. Portanto, o compilador irá expandir a macro para:

1
int* p, q;

Ou seja, você tentou declarar q como um ponteiro, mas após a expansão percebemos que ele na verdade foi declarado como um inteiro. Nestes casos sempre use typedef.

DICA 4: Declare funções simples através de macros para otimizar tempo de processamento, já que evita o custo da chamada de uma função.

Compiladores aderentes ao padrão C99 suportam funções inline. Neste caso, prefira o uso das funções inline, já que o compilador faz algumas verificações adicionais, como tipagem de variáveis.

DICA 5: Cuidado com precedência de operador. Veja o trecho de código baixo:

1
2
#define MULT(a, b) (a * b)
res = MULT(2 + 2, 4)

O compilador irá expandir para:

1
res = (2 + 2 * 4)

Como o operador “*” tem precedencia sobre o operador “+”, o resultado do calculo será 10, mas o correto seria 16. Conclusão: sempre coloque parênteses em todos os elementos da macro.

1
#define MULT(a, b) ((a) * (b))

DICA 6: Cuidado com operadores unários dentro de macros.

Operadores de incremento/decremento podem ser um problema quando usados na chamada à macros:

1
2
#define MIN(a,b) ((a)>(b)?(b):(a))
min = MIN(a++, b);

O compilador irá expandir a macro assim:

1
#define MIN(a,b) ((a++)>(b)?(b):(a++))

Veja que, se o “a” for maior que o “b”, ele será incrementado mais de uma vez.

DICA 7: Expansão de macros dentro de laços.

O exemplo abaixo parece estar sem problemas:

1
2
3
4
5
6
7
8
#define CMDS   \
    a = b;     \
    c = d
 
    if (var == 13)
        CMDS;
    else
        return;

Só que este código não compila porque nossa macro possui mais de uma linha e quando expande no “if” o compilador reclama. Fechando o bloco do “if” conforme abaixo resolvemos o problema.

1
2
3
4
5
6
    if (var == 13) {
        CMDS;
    }
    else {
        return;
    }

DICA 8: Aproveitando o exemplo acima, veja que é possível declarar uma macro com mais de uma linha usando a barra invertida “\” no final de cada linha.

DICA 9: E para finalizar, macros podem ser utillizadas em situações nada convencionais, mas que podem facilitar bastante a leitura do codigo.

O trecho de codigo abaixo tem o objetivo de varrer uma lista de cidades e preencher uma estrutura com o nome e a quantidade de habitantes desta cidade:

1
2
3
4
5
6
7
8
    for (cidade = SAOPAULO; cidade < NUM_CIDADES; cidade++) {
        switch(cidade) {
            CASE(SAOPAULO, 11037);
            CASE(RIODEJANEIRO, 11037);
            CASE(CURITIBA, 11037);
            CASE(BELOHORIZONTE, 11037);
        }
    }

Calma aí, que construções estranhas são estas? Switch sem case nem break? Quando olhamos a declaração da macro CASE() tudo fica mais claro:

1
2
3
4
5
6
#define CASE(cidade, hab)                       \
    case cidade:                                \
        cidades[cidade].habitantes = hab;       \
        strncpy(cidades[cidade].nome, #cidade,  \
            sizeof (cidades[cidade].nome));     \
        break

Perceba que dentro de uma macro é possivel converter um parametro para uma string literal com o caractere “#”.

Estas foram algumas das dicas que pude juntar quando trabalhamos com macros em linguagem C. Se vocês tiverem outros exemplos ou dicas deixem seus comentários por aqui!

Um abraço a todos,

Sergio Prado

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.