Simplifique: use Macros (ou não)

Em 12/02/2010, em Linguagem C, por Sergio Prado

Qual­quer tec­nolo­gia pode ser usada tanto para o bem quanto para o mal. Não é difer­ente quando falamos de macros. Se bem uti­lizada, pode facil­i­tar em muito nossa vida, mel­ho­rar a leitura do código e até mesmo otimizá-lo. Mas se mal uti­lizada, podem nos causar uma tremenda dor de cabeça e uma busca inter­minável por bugs no código.

Mas o que são macros? Anal­isando de uma forma mais sim­ples, macros são basi­ca­mente sub­sti­tu­ições de strings. Macros são tratadas (expandi­das) pelo pré-processador e cri­adas em lin­guagem C através da dire­tiva #define.

Macros são de dois prin­ci­pais tipos:

a. Objeto: nor­mal­mente uti­lizado para dar nome à con­stantes. Exemplo:

1
#define PI 3.1415926

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

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

Seu uso é rel­a­ti­va­mente sim­ples, mas se não prestar­mos atenção à alguns detal­hes de como o pré-processador expande as macros, podemos ficar um tempo “batendo cabeça” até desco­brir­mos o prob­lema. Vamos então apre­sen­tar algu­mas dicas sobre quando deve­mos ou não usar macros, e em alguns pon­tos que pre­cisamos prestar atenção.

DICA 1: Usar sem­pre maiús­cu­las 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ági­cos no código. Essa ori­en­tação é antiga, mas muitos ainda come­tem este erro. Por exem­plo, você tem um buffer de comu­ni­caçã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ê pre­cisa alterar o buffer de comu­ni­cação para 512, e sai bus­cando este número mágico em todo o seu código de mais de 10.000 lin­has. Tudo pode­ria 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éc­nica torna o código mais portável, porém a exe­cução não é das mel­hores, espe­cial­mente quanto trata­mos com var­iáveis tipo pon­teiro. Imag­ine uma macro de um pon­teiro para inteiro:

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

Lembre-se de que a macro é ape­nas uma sub­sti­tu­ição de strings. Por­tanto, o com­pi­lador irá expandir a macro para:

1
int* p, q;

Ou seja, você ten­tou declarar q como um pon­teiro, mas após a expan­são percebe­mos que ele na ver­dade foi declar­ado como um inteiro. Nestes casos sem­pre use typedef.

DICA 4: Declare funções sim­ples através de macros para otimizar tempo de proces­sa­mento, já que evita o custo da chamada de uma função.

Com­pi­ladores ader­entes ao padrão C99 supor­tam funções inline. Neste caso, pre­fira o uso das funções inline, já que o com­pi­lador faz algu­mas ver­i­fi­cações adi­cionais, como tipagem de variáveis.

Dica 5: Cuidado com pre­cedên­cia de oper­ador. Veja o tre­cho de código baixo:

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

O com­pi­lador irá expandir para:

1
res = (2 + 2 * 4)

Como o oper­ador “*” tem prece­den­cia sobre o oper­ador “+”, o resul­tado do cal­culo será 10, mas o cor­reto seria 16. Con­clusão: sem­pre coloque parên­te­ses em todos os ele­men­tos da macro.

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

DICA 6: Cuidado com oper­adores unários den­tro de macros.

Oper­adores de incremento/decremento podem ser um prob­lema quando usa­dos na chamada à macros:

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

O com­pi­lador 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á incre­men­tado mais de uma vez.

DICA 7: Expan­são de macros den­tro de laços.

O exem­plo 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 com­pila porque nossa macro pos­sui mais de uma linha e quando expande no “if” o com­pi­lador reclama. Fechando o bloco do “if” con­forme abaixo resolve­mos o problema.

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

DICA 8: Aprovei­tando o exem­plo acima, veja que é pos­sível declarar uma macro com mais de uma linha usando a barra inver­tida “” no final de cada linha.

DICA 9: E para finalizar, macros podem ser util­lizadas em situ­ações nada con­ven­cionais, mas que podem facil­i­tar bas­tante a leitura do codigo.

O tre­cho de codigo abaixo tem o obje­tivo de var­rer uma lista de cidades e preencher uma estru­tura com o nome e a quan­ti­dade de habi­tantes 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 con­struções estra­nhas são estas? Switch sem case nem break? Quando olhamos a declar­açã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 den­tro de uma macro é pos­sivel con­verter um para­metro para uma string lit­eral com o caractere “#”.

Estas foram algu­mas das dicas que pude jun­tar quando tra­bal­hamos com macros em lin­guagem C. Se vocês tiverem out­ros exem­p­los ou dicas deixem seus comen­tários por aqui!

Um abraço a todos,

Ser­gio Prado

VN:F [1.9.13_1145]
Rat­ing: 10.0/10 (3 votes cast)
Sim­pli­fique: use Macros (ou não), 10.0 out of 10 based on 3 ratings

Posts rela­ciona­dos:

  1. Otimiza­ção de código em Lin­guagem C — Parte 1
  • http://www.lfamorim.com Lucas Fer­nando Amorim

    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.

    VA:F [1.9.13_1145]
    Rating: 0.0/5 (0 votes cast)
  • Marcelo

    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

    VA:F [1.9.13_1145]
    Rating: 0.0/5 (0 votes cast)
  • http://www.embarcados.com.br Ser­gio Prado

    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

    VA:F [1.9.13_1145]
    Rating: 0.0/5 (0 votes cast)
  • Pingback: Trabalhando com o stack em linguagem C - Parte 1