Simplifique: use Macros (ou não)
- por Sergio Prado
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