Simplifique: use Macros (ou não)

Em 12/02/2010, em Linguagem C, por sergioprado
Este artigo fala sobre a imple­men­tação de macros em C e dá algu­mas dicas de quando deve­mos ou não usá-las.
Com­par­tilhe!
  • Twitter
  • Facebook
  • LinkedIn
  • del.icio.us
  • Digg
  • email
  • PDF
  • Print

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.

Alguns compiladores, como o GCC, 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. Só lembre-se de que funções inline não fazem parte do padrão ANSI.

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 FUNC   \
    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

VN:F [1.9.0_1079]
Rating: 0.0/10 (0 votes cast)
Compartilhe!
  • Twitter
  • Facebook
  • LinkedIn
  • del.icio.us
  • Digg
  • email
  • PDF
  • Print

Posts relacionados:

  1. Otimização de código em Linguagem C — Parte 1
  2. Análise estática de código
  3. Otimização de código em Linguagem C — Parte 2

3 Respostas para “Simplifique: use Macros (ou não)”

  1. Parabéns pelo blog, rec­heado de arti­gos muito bem escritos e úteis. Gostei muito deste a respeito de macros, é um mate­r­ial muito bom.

  2. Marcelo Marcelo disse:

    Ola Ser­gio, parabéns pelo con­teúdo do blog e a ati­tude de com­par­til­har o seu grande con­hec­i­mento conosco.

    Eu gostaria de ini­ciar nesta área como você, eu tenho con­hec­i­mento inter­mediário em C e estou cur­sando Engen­haria da com­putação, tenho inter­esse nesta área de sis­temas Embar­ca­dos, quais livros você pode­ria recomen­dar para leitura, e quais mate­ri­ais são necessário para mon­tar um lab­o­ratório caseiro, para apreen­der, desen­volver e atuar nesta área?

    Sei que o cam­inho não é fácil, mas gostaria que você pudesse mostrar o cam­inho das pedras.…

    Obri­gado
    Marcelo

  3. Sergio Prado Sergio Prado disse:

    Olá Marcelo,

    Sua per­gunta é muito inter­es­sante, e me remete a uns dez anos atrás, quando eu mesmo me fazia esta per­gunta, e vas­cul­hava em forums e doc­u­men­tos na inter­net por uma resposta.

    E a resposta a esta per­gunta pode se tornar na ver­dade tão extensa que vale até um post sep­a­rado para a próx­ima semana.

    Um abraço e até lá!

    Ser­gio Prado

Deixe um comentário