C Macros com Parâmetros

Na aula Pré-processador, o #define trocava um nome por um valor fixo: PRECO virava 12. Uma macro também pode receber parâmetros, como uma função: QUADRADO(4) vira ((4) * (4)). Parece uma função, mas não é. É troca de texto, e essa diferença produz alguns dos bugs mais famosos do C. Nesta aula você vai escrever macros com parâmetros, ver cada armadilha acontecer de verdade e aprender as regras que os programadores de C usam para se proteger delas.

Você vai aprender
  • Como criar uma macro com parâmetros: #define QUADRADO(x) ((x) * (x)).
  • Por que QUADRADO(2+3) pode dar 11, e a regra dos parênteses.
  • Por que QUADRADO(i++) soma 2 no i (o efeito duplo).
  • Os operadores # (transforma em texto) e ## (cola dois pedaços).
  • Como escrever macros de várias linhas com \ e por que elas usam do { … } while (0).
  • Macros com número variável de argumentos (... e __VA_ARGS__).
  • Quando usar uma função inline em vez de uma macro.

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c/macros, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Para entender cada macro, vamos usar muito o gcc -E -P, que mostra o código depois do pré-processamento (veja Pré-processador).


A ideia: um carimbo com lacuna

Pense num carimbo de papelaria escrito "Pago: R$ ____". Toda vez que você carimba, escreve um valor diferente na lacuna. O carimbo não confere nada: se você escrever "banana" na lacuna, o papel fica "Pago: R$ banana".

Uma macro com parâmetro é esse carimbo. O #define diz o texto do carimbo, e o parâmetro é a lacuna. Onde você escreve QUADRADO(4), o pré-processador carimba o texto da macro e escreve 4 em cada lacuna. Ele não calcula, não confere o tipo, não sabe o que é um número. Uma função de verdade (aula Funções) é diferente: ela recebe o valor já calculado, confere o tipo e roda quando o programa roda.

Macro com parâmetroFunção
Quem cuidao pré-processador, antes de compilaro compilador, e ela roda com o programa
O que recebeo texto que você escreveuo valor já calculado
Confere o tipo?nãosim
O argumento é calculadoquantas vezes ele aparecer na macrouma vez só

1. A primeira macro com parâmetro

A Ana quer calcular a área de mesas quadradas para a lojinha. Ela escreve uma macro que eleva um número ao quadrado:

#include <stdio.h>

#define QUADRADO(x) x * x

int main(void) {
    printf("QUADRADO(4)   = %d\n", QUADRADO(4));
    printf("QUADRADO(2+3) = %d\n", QUADRADO(2+3));
    return 0;
}
QUADRADO
O nome da macro, em maiúsculas por costume.
(x)
A lista de parâmetros, colada no nome, sem espaço. O x é a lacuna do carimbo.
x * x
O texto de troca. Cada x será trocado pelo texto que estiver entre os parênteses na hora do uso.
$ gcc -std=c23 -Wall -Wextra quadrado.c -o quadrado && ./quadrado
QUADRADO(4)   = 16
QUADRADO(2+3) = 11          « 2+3 é 5, e 5 ao quadrado é 25, não 11!

Nenhum erro, nenhum aviso, e uma resposta errada. Este é o bug mais famoso das macros. O gcc -E -P mostra o motivo. Num arquivo pequeno, sem #include, o resultado fica curto:

#define QUADRADO(x) x * x
#define QUADRADO2(x) (x) * (x)
#define QUADRADO3(x) ((x) * (x))

int a = QUADRADO(2+3);
int b = 100 / QUADRADO2(5);
int c = 100 / QUADRADO3(5);
$ gcc -E -P mini.c
int a = 2+3 * 2+3;              « o texto "2+3" entrou em cada x
int b = 100 / (5) * (5);
int c = 100 / ((5) * (5));

Olhe a primeira linha. O pré-processador não calculou 2+3: ele copiou o texto 2+3 para cada x e entregou 2+3 * 2+3 ao compilador. A multiplicação vem antes da soma (aula Precedência), então a conta é 2 + 6 + 3 = 11.

Parênteses em cada parâmetro…

O primeiro conserto é cercar cada x de parênteses, como no QUADRADO2: (2+3) * (2+3) dá 25. Mas a segunda linha da saída mostra que ainda falta algo:

#include <stdio.h>

#define QUADRADO(x) (x) * (x)

int main(void) {
    printf("QUADRADO(2+3)     = %d\n", QUADRADO(2+3));
    printf("100 / QUADRADO(5) = %d\n", 100 / QUADRADO(5));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra meio.c -o meio && ./meio
QUADRADO(2+3)     = 25
100 / QUADRADO(5) = 100          « 5 ao quadrado é 25, e 100 / 25 é 4!

O texto virou 100 / (5) * (5). Divisão e multiplicação têm a mesma força e são feitas da esquerda para a direita: 100 / 5 = 20, depois 20 * 5 = 100.

…e em volta da macro inteira

#include <stdio.h>

#define QUADRADO(x) ((x) * (x))

int main(void) {
    printf("QUADRADO(2+3)     = %d\n", QUADRADO(2+3));
    printf("100 / QUADRADO(5) = %d\n", 100 / QUADRADO(5));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra certo.c -o certo && ./certo
QUADRADO(2+3)     = 25
100 / QUADRADO(5) = 4
A regra dos parênteses. Numa macro que calcula um valor, ponha parênteses em volta de cada uso de cada parâmetro e em volta do texto inteiro: ((x) * (x)), ((a) + (b)). Os de dentro protegem contra o que entra; os de fora protegem contra o que está em volta. Parece exagero, e é de propósito.

Missão: rode e veja os dois resultados errados. Depois conserte só a linha do #define, com a regra dos parênteses, até aparecer 25 e 4.

#include <stdio.h>

#define QUADRADO(x) x * x

int main(void) {
    printf("QUADRADO(2+3) = %d\n", QUADRADO(2+3));
    printf("100 / QUADRADO(5) = %d\n", 100 / QUADRADO(5));
    return 0;
}
#include <stdio.h>

#define QUADRADO(x) ((x) * (x))

int main(void) {
    printf("QUADRADO(2+3) = %d\n", QUADRADO(2+3));
    printf("100 / QUADRADO(5) = %d\n", 100 / QUADRADO(5));
    return 0;
}

2. O efeito duplo: o argumento roda duas vezes

Os parênteses resolvem a conta, mas não resolvem tudo. Na macro ((x) * (x)), o x aparece duas vezes. Então o que você escrever ali será copiado, e executado, duas vezes. Com um número, tanto faz. Com algo que muda uma variável, não:

#include <stdio.h>

#define QUADRADO(x) ((x) * (x))

int main(void) {
    int i = 3;
    int q = QUADRADO(i++);
    printf("q = %d, i = %d\n", q, i);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra duplo.c -o duplo && ./duplo
duplo.c: In function ‘main’:
duplo.c:7:23: warning: operation on ‘i’ may be undefined [-Wsequence-point]
    7 |     int q = QUADRADO(i++);
      |                       ^
duplo.c:3:29: note: in definition of macro ‘QUADRADO’
    3 | #define QUADRADO(x) ((x) * (x))
      |                             ^
q = 12, i = 5          « era para ser q = 9 e i = 4

A linha 7 virou int q = ((i++) * (i++));. O i++ (aula ++ e --) aparece duas vezes, então o i subiu duas vezes: de 3 para 5. E pior: mudar a mesma variável duas vezes na mesma conta é comportamento indefinido em C (aula Comportamento Indefinido): a norma não diz qual resultado sai. O gcc deu 12; outro compilador pode dar outra coisa. Por isso o aviso: "a operação em i pode ser indefinida". O Clang do editor avisa a mesma coisa com outras palavras: multiple unsequenced modifications to 'i'.

Mesmo sem ++, o efeito duplo aparece com funções. A macro MAIOR devolve o maior de dois números, e a Ana passa a ela uma função que lê uma nota:

#include <stdio.h>

#define MAIOR(a, b) ((a) > (b) ? (a) : (b))

int ler_nota(void) {
    printf("  (lendo a nota...)\n");
    return 8;
}

int main(void) {
    int m = MAIOR(ler_nota(), 5);
    printf("Maior: %d\n", m);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra maior.c -o maior && ./maior
  (lendo a nota...)
  (lendo a nota...)          « a função rodou duas vezes!
Maior: 8
$ gcc -E -P maior.c | tail -4
    int m = ((ler_nota()) > (5) ? (ler_nota()) : (5));
    printf("Maior: %d\n", m);
    return 0;
}

Repare: a macro tem dois parâmetros, separados por vírgula, e usa o operador ternário (aula Operador Ternário). Como a aparece duas vezes no texto, ler_nota() é chamada uma vez para comparar e outra para devolver o valor. Se a função lesse o teclado, a Ana teria de digitar a nota duas vezes. E desta vez nenhum aviso apareceu.

Regra: nunca passe para uma macro algo que muda alguma coisa: i++, x = 5, scanf(…), uma função que imprime ou lê. Passe só nomes de variáveis e contas simples. Como ninguém lembra disso o tempo todo, a solução de verdade é usar uma função (seção 6).

3. #: transformar o argumento em texto

Dentro de uma macro, um # antes do nome do parâmetro transforma o argumento em um texto entre aspas. O nome técnico é stringification, "transformar em string". Com isso, a Ana cria uma macro que mostra o nome da variável e o valor dela, ótima para depurar:

#include <stdio.h>

#define MOSTRA(x) printf(#x " = %d\n", x)

int main(void) {
    int cadernos = 3;
    int preco = 12;
    MOSTRA(cadernos);
    MOSTRA(preco);
    MOSTRA(cadernos * preco);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra mostra.c -o mostra && ./mostra
cadernos = 3
preco = 12
cadernos * preco = 36
$ gcc -E -P mostra.c | tail -6
    int preco = 12;
    printf("cadernos" " = %d\n", cadernos);
    printf("preco" " = %d\n", preco);
    printf("cadernos * preco" " = %d\n", cadernos * preco);
    return 0;
}
#x
Vira o texto do argumento entre aspas: "cadernos", "cadernos * preco".
" = %d\n"
Um segundo texto, logo ao lado.
x
O argumento normal, sem #: vira o valor que o printf mostra.

E como "cadernos" " = %d\n" funciona no printf? Em C, dois textos entre aspas lado a lado, separados só por espaço, são colados pelo compilador num texto só: "cadernos = %d\n". Isso vale em qualquer lugar do código, não só em macros.

Experimente: rode. Depois acrescente MOSTRA(preco * 2); e MOSTRA(cadernos + 1); e veja a conta aparecer escrita na saída, do jeito que você digitou.

#include <stdio.h>

#define MOSTRA(x) printf(#x " = %d\n", x)

int main(void) {
    int cadernos = 3;
    int preco = 12;
    MOSTRA(cadernos);
    MOSTRA(preco);
    MOSTRA(cadernos * preco);
    return 0;
}

4. ##: colar dois pedaços num nome só

O ## cola o que está à esquerda com o que está à direita, formando uma palavra só. O nome técnico é token pasting, "colar pedaços":

#include <stdio.h>

#define PRECO(item) preco_##item

int main(void) {
    int preco_caderno = 12;
    int preco_lapis = 2;
    printf("Caderno: %d\n", PRECO(caderno));
    printf("Lápis:   %d\n", PRECO(lapis));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra cola.c -o cola && ./cola
Caderno: 12
Lápis:   2

PRECO(caderno) virou preco_caderno, o nome da variável. Sem o ##, preco_item seria uma palavra só e o item nem seria trocado. Você vai ver ## em bibliotecas que geram várias funções parecidas de uma vez (uma para int, outra para double…). No dia a dia, use com moderação: um nome que só aparece depois do pré-processamento é difícil de achar com a busca do editor.


5. Macros de várias linhas e o do { } while (0)

Uma diretiva termina no fim da linha. Para continuar na linha de baixo, termine a linha com uma barra invertida \. Ela tem de ser o último caractere da linha (nem um espaço depois).

A Ana quer uma macro VENDER que mostra a venda e soma 1 no contador. São dois comandos. A primeira tentativa:

#include <stdio.h>

int vendas = 0;
#define VENDER(item) printf("Vendido: %s\n", item); vendas++

int main(void) {
    int tem_estoque = 0;
    if (tem_estoque)
        VENDER("caderno");
    printf("Vendas: %d\n", vendas);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra conta_ruim.c -o conta_ruim && ./conta_ruim
conta_ruim.c: In function ‘main’:
conta_ruim.c:4:22: warning: macro expands to multiple statements [-Wmultistatement-macros]
    4 | #define VENDER(item) printf("Vendido: %s\n", item); vendas++
      |                      ^~~~~~
conta_ruim.c:9:9: note: in expansion of macro ‘VENDER’
    9 |         VENDER("caderno");
      |         ^~~~~~
conta_ruim.c:8:5: note: some parts of macro expansion are not guarded by this ‘if’ clause
    8 |     if (tem_estoque)
      |     ^~
Vendas: 1          « não vendeu nada, mas contou uma venda!

Sem estoque, nada foi vendido, mas o contador subiu. O if sem chaves comanda só um comando (aula if e else). Depois da troca, o código ficou assim:

    if (tem_estoque)
        printf("Vendido: %s\n", "caderno");
    vendas++;                       // fora do if: roda sempre

O gcc percebeu e avisou: "a macro se expande em vários comandos" e "algumas partes da expansão não estão protegidas por este if". O Clang do editor não dá esse aviso: lá, o programa simplesmente mostra Vendas: 1.

Chaves resolvem? Quase.

A ideia natural é pôr chaves em volta: { printf(…); vendas++; }. Agora o bloco todo fica dentro do if. Mas veja o que acontece com um else, nesta macro que troca dois valores:

#include <stdio.h>

#define TROCA(a, b) { int tmp = a; a = b; b = tmp; }

int main(void) {
    int x = 1, y = 2;
    if (x < y)
        TROCA(x, y);
    else
        printf("Já estava em ordem\n");
    printf("x = %d, y = %d\n", x, y);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra troca_ruim.c -o troca_ruim
troca_ruim.c: In function ‘main’:
troca_ruim.c:9:5: error: ‘else’ without a previous ‘if’
    9 |     else
      |     ^~~~

"else sem um if antes." O culpado é o ; que você escreve depois de TROCA(x, y), como em qualquer comando. O texto ficou if (x < y) { … } ; else. O if termina no }, o ; é um comando vazio, e o else fica sem par. (No editor, o Clang diz só error: expected expression, na mesma linha.)

O truque: do { … } while (0)

Os programadores de C resolveram isso com um laço que roda exatamente uma vez. O do { } while (0) (aula do while) junta vários comandos num só e exige o ; no final, como um comando comum:

#include <stdio.h>

int vendas = 0;
#define VENDER(item)                       \
    do {                                   \
        printf("Vendido: %s\n", item);     \
        vendas++;                          \
    } while (0)

int main(void) {
    int tem_estoque = 0;
    if (tem_estoque)
        VENDER("caderno");
    else
        printf("Sem estoque!\n");
    printf("Vendas: %d\n", vendas);
    return 0;
}
\
No fim de cada linha: "a macro continua na linha de baixo". O alinhamento das barras é só para ficar bonito.
do {
Abre o bloco com os comandos da macro.
} while (0)
"Repita enquanto 0": 0 é falso, então o bloco roda uma vez e acaba. Sem ; aqui: quem põe o ; é quem usa a macro.
$ gcc -std=c23 -Wall -Wextra conta_boa.c -o conta_boa && ./conta_boa
Sem estoque!
Vendas: 0

Sem aviso, sem erro, e o contador certo. O texto agora é if (tem_estoque) do { … } while (0); else …: o do … while (0); é um comando, o if comanda tudo e o else encontra o seu par. O compilador some com o laço na hora de gerar o programa; ele não custa nada.

Macros com número variável de argumentos C99

Uma macro pode receber "quantos argumentos vierem", com ... na lista e __VA_ARGS__ no texto (variable arguments, "argumentos variáveis"). Antes, um uso que mostra por que macros ainda existem. A Ana quer mensagens de depuração que digam a linha de onde vieram:

#include <stdio.h>

#define LOG(msg) fprintf(stderr, "[%s:%d] %s\n", __FILE__, __LINE__, msg)

int main(void) {
    int estoque = 2;
    LOG("Programa começou");
    if (estoque < 5)
        LOG("Estoque baixo!");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra log.c -o log && ./log
[log.c:7] Programa começou
[log.c:9] Estoque baixo!

Cada mensagem mostra a sua própria linha, porque o __LINE__ foi carimbado no lugar onde o LOG foi usado. Uma função log mostraria sempre a linha de dentro dela. O fprintf(stderr, …) escreve na saída de erros (aula stdin, stdout e stderr).

Para o LOG aceitar um formato com valores, como o printf, use .... O __VA_OPT__(,) C23 põe a vírgula só quando vieram valores extras:

#include <stdio.h>

#define LOG(fmt, ...) \
    fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__)

int main(void) {
    int estoque = 2;
    LOG("Programa começou");
    LOG("Estoque baixo: %d unidades de %s", estoque, "caderno");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra logv.c -o logv && ./logv
[logv.c:8] Programa começou
[logv.c:9] Estoque baixo: 2 unidades de caderno

No segundo LOG, fmt é o texto com os %, e __VA_ARGS__ é estoque, "caderno". Antes do C23 não existia __VA_OPT__, e cada compilador tinha o seu truque (o gcc aceita , ##__VA_ARGS__). Com -std=c17 -pedantic, o gcc avisa: ‘__VA_OPT__’ is not available until C23. Funções com número variável de argumentos de verdade têm aula própria: Funções Variádicas.


6. Macro ou função inline?

Quase tudo o que uma macro calcula, uma função calcula melhor. A palavra inline C99 sugere ao compilador "copie o código desta função no lugar da chamada", que era a vantagem de velocidade das macros. E o static diz que ela é só deste arquivo (aula static e extern). Veja a mesma conta como função:

#include <stdio.h>

static inline int quadrado(int x) {
    return x * x;
}

int main(void) {
    int i = 3;
    printf("quadrado(2+3) = %d\n", quadrado(2+3));
    int q = quadrado(i++);
    printf("q = %d, i = %d\n", q, i);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra inline.c -o inline && ./inline
quadrado(2+3) = 25
q = 9, i = 4

Sem um parêntese a mais, quadrado(2+3) dá 25: a função recebe o valor 5, e não o texto. E quadrado(i++) dá 9 com i valendo 4: o argumento é calculado uma vez só. A função também confere o tipo. Passe um texto para a macro e para a função e compare as mensagens:

$ gcc -std=c23 -Wall -Wextra tipo.c -o tipo
tipo.c: In function ‘main’:
tipo.c:3:26: error: invalid operands to binary * (have ‘char *’ and ‘char *’)
    3 | #define QUADRADO(x) ((x) * (x))
      |                      ~   ^
      |                      |   |
      |                      |   char *
      |                      char *
tipo.c:6:20: note: in expansion of macro ‘QUADRADO’
    6 |     printf("%d\n", QUADRADO("oi"));
      |                    ^~~~~~~~

Com a macro, o erro aponta para dentro do #define (linha 3), com uma nota dizendo onde ela foi usada. Com a função quadrado("oi"), o gcc diz direto na linha da chamada: passing argument 1 of ‘quadrado’ makes integer from pointer without a cast ("passar o argumento 1 de quadrado transforma um ponteiro em inteiro"). Num programa grande, a diferença é enorme.

Use…Quando
Função (com static inline se for pequena e usada só no arquivo)Para calcular algo: contas, comparações, conversões. É a escolha padrão.
Macro com parâmetrosQuando precisa de algo que só o pré-processador faz: # (nome da variável como texto), ##, __FILE__/__LINE__ do lugar de uso, ou servir para vários tipos ao mesmo tempo (para isso o C11 também tem _Generic).

Mais sobre inline na aula Funções inline.


Quando dá errado

error: ‘x’ undeclared apontando para dentro do #define
$ gcc -std=c23 -Wall -Wextra espaco.c -o espaco
espaco.c: In function ‘main’:
espaco.c:3:19: error: ‘x’ undeclared (first use in this function)
    3 | #define QUADRADO (x) ((x) * (x))
      |                   ^
espaco.c:6:20: note: in expansion of macro ‘QUADRADO’
    6 |     printf("%d\n", QUADRADO(4));
      |                    ^~~~~~~~
   «  … a mesma nota repetida …

O que significa: há um espaço entre QUADRADO e (x). Com o espaço, o pré-processador entende uma macro sem parâmetros, cujo texto é (x) ((x) * (x)). Aí QUADRADO(4) vira (x) ((x) * (x))(4), e o x não existe.

Como consertar: o parêntese dos parâmetros vem colado no nome: #define QUADRADO(x) …. No uso, tanto faz: QUADRADO (4) funciona.

error: macro ‘QUADRADO’ passed 2 arguments, but takes just 1
$ gcc -std=c23 -Wall -Wextra args.c -o args
args.c: In function ‘main’:
args.c:6:33: error: macro ‘QUADRADO’ passed 2 arguments, but takes just 1
    6 |     printf("%d\n", QUADRADO(3, 4));
      |                                 ^
args.c:3:9: note: macro ‘QUADRADO’ defined here
    3 | #define QUADRADO(x) ((x) * (x))
      |         ^~~~~~~~
   «  … e um erro ‘QUADRADO’ undeclared, consequência do primeiro …

O que significa: "a macro recebeu 2 argumentos, mas aceita só 1". A vírgula separa argumentos, como numa função.

Como consertar: passe o número certo de argumentos. Se o argumento precisa ter uma vírgula de verdade dentro, ponha-o entre parênteses: MOSTRA((a, b)).

Resultado errado, sem erro nem aviso

O que significa: quase sempre, faltam parênteses na macro (QUADRADO(2+3) dando 11) ou um argumento foi calculado duas vezes (uma função chamada duas vezes, como em MAIOR(ler_nota(), 5)).

Como consertar: rode gcc -E -P arquivo.c e leia o texto que o compilador recebeu. Aplique a regra dos parênteses. Se o argumento tem efeito (++, função), guarde o valor numa variável antes, ou troque a macro por uma função.

warning: macro expands to multiple statements / error: ‘else’ without a previous ‘if’

O que significa: uma macro com vários comandos foi usada num if sem chaves. Sem chaves na macro, só o primeiro comando fica dentro do if. Com chaves mas sem do … while (0), o ; depois do uso separa o if do else.

Como consertar: escreva a macro como do { … } while (0), sem ; no final. E, de quebra, use sempre chaves nos seus if.

warning: backslash and newline separated by space

O que significa: numa macro de várias linhas, há um espaço depois da \. O gcc avisa ("barra invertida e quebra de linha separadas por espaço") e aceita assim mesmo, mas outros compiladores podem terminar a macro ali.

Como consertar: apague os espaços depois da \. Ela tem de ser o último caractere da linha.


Referência

EscritaO que faz
#define F(x) ((x) * 2)Macro com um parâmetro (parênteses colados no nome)
#define F(a, b) ((a) + (b))Macro com dois parâmetros
#xTransforma o argumento em texto: MOSTRA(preco) → "preco"
a##bCola dois pedaços num nome: preco_##item → preco_caderno
\ no fim da linhaA macro continua na linha de baixo
do { … } while (0)Junta vários comandos num só, seguro em if/else
#define F(fmt, ...) + __VA_ARGS__Número variável de argumentos C99
__VA_OPT__(,)Põe a vírgula só se vieram argumentos extras C23
static inline int f(int x) { … }A alternativa com tipo e cálculo único
gcc -E -P arquivo.cMostra o texto depois das trocas

Exercícios

1. Parênteses

Complete a macro DOBRO com a regra dos parênteses:

#define DOBRO(n)

2. Nome como texto

Complete para que NOME(idade) vire o texto "idade":

#define NOME(v)

3. Com #define DOBRO(n) n * 2, quanto vale DOBRO(1+4)?

4. Com #define MAIOR(a, b) ((a) > (b) ? (a) : (b)), quantas vezes ler() pode ser chamada em MAIOR(ler(), 0)?

5. Por que as macros de vários comandos usam do { … } while (0)?

6. A Ana quer calcular a média de duas notas. Qual é a melhor escolha?

Missão final: o caixa da lojinha

Missão: o programa não compila: a macro VENDER tem dois comandos e quebra o if/else. Reescreva só a macro com do { … } while (0) (use \ no fim das linhas, ou escreva tudo numa linha só). A saída deve ficar assim, sem avisos:

Vendido: lápis
Sem estoque: caderno
Vendas: 1
#include <stdio.h>

int vendas = 0;

#define VENDER(item) printf("Vendido: %s\n", item); vendas++

void atender(const char *item, int estoque) {
    if (estoque > 0)
        VENDER(item);
    else
        printf("Sem estoque: %s\n", item);
}

int main(void) {
    atender("lápis", 5);
    atender("caderno", 0);
    printf("Vendas: %d\n", vendas);
    return 0;
}
#include <stdio.h>

int vendas = 0;

#define VENDER(item)                   \
    do {                               \
        printf("Vendido: %s\n", item); \
        vendas++;                      \
    } while (0)

void atender(const char *item, int estoque) {
    if (estoque > 0)
        VENDER(item);
    else
        printf("Sem estoque: %s\n", item);
}

int main(void) {
    atender("lápis", 5);
    atender("caderno", 0);
    printf("Vendas: %d\n", vendas);
    return 0;
}

Resumo

  • Uma macro com parâmetros (#define F(x) …, parêntese colado no nome) é um carimbo: o pré-processador copia o texto do argumento em cada lacuna. Não calcula e não confere o tipo.
  • Regra dos parênteses: em volta de cada parâmetro e da macro inteira, ((x) * (x)). Sem eles, QUADRADO(2+3) dá 11 e 100 / QUADRADO(5) dá 100.
  • Efeito duplo: o argumento roda tantas vezes quantas aparece. Nunca passe i++ ou funções com efeito para uma macro.
  • #x transforma o argumento em texto; a##b cola dois pedaços num nome.
  • Macro com vários comandos: \ no fim das linhas e do { … } while (0), sem ; no fim.
  • ... e __VA_ARGS__ (C99) aceitam vários argumentos; __VA_OPT__ (C23) resolve a vírgula.
  • Para calcular, prefira uma função (static inline). Deixe as macros para o que só o pré-processador faz. Para enxergar qualquer macro: gcc -E -P.