Macros são uma ferramenta poderosa da linguagem C, fornecida pelo pré-processador. Elas permitem definir substituições de texto que ocorrem antes da compilação, possibilitando desde constantes simbólicas até pequenas funções inline. Nesta aula, exploraremos como criar macros, os cuidados necessários para evitar comportamentos inesperados e operadores especiais como # e ##.

Macros simples e com argumentos

Uma macro simples é definida com #define e substitui um identificador por uma sequência de tokens. Por exemplo:

#define PI 3.14159
#define ERRO "Mensagem de erro"

Macros com argumentos funcionam como funções, mas são expandidas textualmente. Exemplo:

#define SOMA(a, b) a + b
#define QUADRADO(x) x * x

No entanto, essa expansão textual pode causar problemas de precedência se não houver parênteses adequados.

Parênteses e armadilhas

A falta de parênteses em macros com argumentos pode levar a resultados errados. Considere:

#define DOBRO(x) x + x
int y = DOBRO(3) * 2; // Esperado: 6*2=12, Real: 3+3*2=9

Para evitar isso, sempre coloque parênteses em torno de cada parâmetro e de toda a expressão:

#define DOBRO(x) ((x) + (x))
#define QUADRADO(x) ((x) * (x))

Outra armadilha comum é a avaliação múltipla de argumentos. Se um argumento tem efeitos colaterais (ex.: i++), ele pode ser avaliado várias vezes:

#define MAX(a, b) ((a) > (b) ? (a) : (b))
int maior = MAX(i++, j++); // i e j incrementados duas vezes

Nesses casos, prefira funções inline (C99) ou evite argumentos com efeitos colaterais.

# e ## (stringify, concat)

O operador # (stringify) converte um parâmetro de macro em uma string literal. Exemplo:

#define STR(x) #x
printf("%s\n", STR(hello)); // imprime "hello"

O operador ## (concatenação) cola dois tokens em um único token. Útil para gerar nomes de variáveis ou funções:

#define VAR(nome, num) nome ## num
int VAR(var, 1) = 10; // int var1 = 10;

Esses operadores são processados durante a expansão da macro e permitem metaprogramação básica em C.

Exemplos

Vamos ver um exemplo completo que combina macros com e sem argumentos, usando # e ##:

#include <stdio.h>

#define PI 3.14159
#define AREA_CIRCULO(r) (PI * (r) * (r))
#define PRINT_VAR(n) printf(#n " = %d\n", n)
#define CONCAT(a, b) a ## b

int main() {
    int x = 5;
    int xy = 10;
    PRINT_VAR(x);
    PRINT_VAR(CONCAT(x, y)); // imprime "xy = 10"
    printf("Area do circulo com raio 2: %f\n", AREA_CIRCULO(2));
    return 0;
}

Outro exemplo prático: macros para depuração:

#define DEBUG_PRINT(msg) printf("DEBUG: %s\n", msg)
#define ASSERT(cond) if (!(cond)) { fprintf(stderr, "Assertion failed: %s\n", #cond); exit(1); }

Macros são amplamente usadas em código C para evitar repetição e melhorar a legibilidade, mas devem ser usadas com cuidado.

Boas práticas

  • Sempre use parênteses em torno de parâmetros e da expressão inteira.
  • Evite efeitos colaterais em argumentos de macros.
  • Prefira funções inline (C99) ou const para constantes quando possível.
  • Use nomes em maiúsculas para macros, por convenção.
  • Considere usar #undef para evitar conflitos.

Referências

Exercícios

  1. Crie uma macro MIN(a, b) que retorna o mínimo entre dois números, com parênteses adequados.

    ✓ Resposta:
    #define MIN(a, b) ((a) < (b) ? (a) : (b))
  2. Qual a saída do código abaixo? Explique.

    #define SOMA(a, b) a + b
    int main() { int x = SOMA(2, 3) * 4; printf("%d", x); return 0; }

    ✓ Resposta: A saída é 14, porque a macro expande para 2 + 3 * 4, que é 2 + 12 = 14. Deveria ser (2+3)*4 = 20. A falta de parênteses causa o erro.
  3. Use o operador # para criar uma macro PRINT_INT(x) que imprime o nome da variável e seu valor inteiro.

    ✓ Resposta:
    #define PRINT_INT(x) printf(#x " = %d\n", x)
  4. Crie uma macro CONCAT3(a, b, c) que concatena três tokens usando ##.

    ✓ Resposta:
    #define CONCAT3(a, b, c) a ## b ## c
  5. Escreva uma macro IS_EVEN(x) que retorna 1 se x for par, 0 caso contrário. Use apenas operadores aritméticos e parênteses.

    ✓ Resposta:
    #define IS_EVEN(x) (((x) % 2) == 0)