O Pré-processador

O pré-processador é como revisar plantas antes da construção — antes da compilação real começar, ele expande macros, insere arquivos de cabeçalho e poda ramos condicionais, depois entrega o resultado ao compilador.

Pipeline de Pré-processamento

O processo de compilação em C: arquivo fonte → pré-processamento → compilação → montagem → ligação. O pré-processador roda antes da compilação, tratando todas as diretivas que começam com #.

Você pode usar a flag -E para ver apenas a saída pré-processada:

BASH
gcc -E hello.c -o hello.i

#include Arquivos de Cabeçalho

#include insere o conteúdo do arquivo especificado literalmente na posição atual.

Duas Formas

C
#include <stdio.h>
#include "myheader.h"
💡 Dica: Use " " para seus próprios cabeçalhos e < > para cabeçalhos da biblioteca padrão — esta é a convenção.

Cabeçalhos Padrão Comuns

Cabeçalho Conteúdo Principal
stdio.h Funções de entrada/saída
stdlib.h Alocação de memória, conversão de tipos, números aleatórios
string.h Operações com strings
math.h Funções matemáticas
ctype.h Classificação de caracteres
assert.h Macro de assert

#define Definições de Macro

Macros de Objeto

Defina constantes nomeadas:

C
#define PI 3.14159265
#define MAX_SIZE 100
#define AUTHOR "WebTutorial"

double area = PI * 10 * 10;
int arr[MAX_SIZE];
printf("Autor: %s\n", AUTHOR);
⚠️ Atenção: Não adicione ponto e vírgula no final de uma definição de macro! Se adicionar, o ponto e vírgula se torna parte da substituição e pode causar erros.

Macros de Função

Macros com parâmetros parecem funções, mas são substituições de texto:

C
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int s = SQUARE(5);
int m = MAX(10, 20);
TEXT
25
20
💡 Dica: O pré-processador faz substituição de texto simples — sem verificação de tipo, sem avaliação de expressão.

Armadilhas de Macros e Parênteses

Armadilha 1: Falta de Parênteses Causa Erros de Precedência

C
#define DOUBLE(x) x + x
C
int result = DOUBLE(3) * 2;

Após a expansão isso se torna 3 + 3 * 2, que resulta em 9 em vez dos esperados 12. Versão correta:

C
#define DOUBLE(x) ((x) + (x))

Armadilha 2: Parâmetros com Efeitos Colaterais

C
#define SQUARE(x) ((x) * (x))
C
int n = 3;
int s = SQUARE(n++);

Após a expansão isso se torna ((n++) * (n++)), incrementando n duas vezes — comportamento indefinido.

⚠️ Atenção: Nunca passe expressões com efeitos colaterais (como i++, func()) como argumentos de macro. Use funções inline ou funções reais em vez disso.

Armadilha 3: Macros em Ramos if-else

C
#define CHECK(cond) if (cond) printf("yes\n")
C
if (flag)
    CHECK(x);
else
    do_something();

Após a expansão, o else corresponde ao if interno da macro, não ao externo — o clássico problema do else pendente. Envolva com do { ... } while(0):

C
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)

Exemplo

C
#include <stdio.h>

#define SAFE_MAX(a, b) ((a) > (b) ? (a) : (b))
#define SAFE_SQUARE(x) ((x) * (x))
#define SWAP(type, a, b) do { type _tmp = (a); (a) = (b); (b) = _tmp; } while(0)

int main(void) {
    int x = 10, y = 20;
    printf("max: %d\n", SAFE_MAX(x, y));
    printf("square: %d\n", SAFE_SQUARE(5));

    SWAP(int, x, y);
    printf("apos troca: x=%d y=%d\n", x, y);

    double a = 3.5, b = 2.1;
    printf("double max: %.1f\n", SAFE_MAX(a, b));
    return 0;
}
▶ Experimente
TEXT
max: 20
square: 25
apos troca: x=20 y=10
double max: 3.5

Compilação Condicional

A compilação condicional permite que o mesmo código fonte produza saídas compiladas diferentes sob diferentes condições. É amplamente usada para suporte multiplataforma, chaves de depuração e remoção de recursos.

#if / #elif / #else / #endif

C
#define PLATFORM 2

#if PLATFORM == 1
const char *os = "Windows";
#elif PLATFORM == 2
const char *os = "Linux";
#else
const char *os = "Unknown";
#endif

printf("Plataforma: %s\n", os);
TEXT
Plataforma: Linux

#ifdef / #ifndef

#ifdef verifica se uma macro está definida; #ifndef verifica se não está definida.

C
#define DEBUG

#ifdef DEBUG
printf("Modo debug: x=%d\n", x);
#endif

#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
💡 Dica: #ifdef DEBUG é equivalente a #if defined(DEBUG), e #ifndef é equivalente a #if !defined(...).

Chave de Depuração

C
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif

LOG("Variavel inicializada");

Compile com -DDEBUG para ativar o log; sem isso, o programa permanece silencioso:

BASH
gcc -DDEBUG -o myapp myapp.c

Exemplo

C
#include <stdio.h>

#define LEVEL 3

int main(void) {
    #if LEVEL >= 3
    printf("Recursos avancados ativados\n");
    #elif LEVEL >= 2
    printf("Recursos intermediarios ativados\n");
    #else
    printf("Recursos basicos\n");
    #endif

    #ifdef VERBOSE
    printf("Modo de saida detalhado\n");
    #else
    printf("Modo de saida conciso\n");
    #endif
    return 0;
}
▶ Experimente
TEXT
Recursos avancados ativados
Modo de saida conciso

Macros Predefinidas

O padrão C predefine várias macros úteis:

Macro Significado
__FILE__ Nome do arquivo fonte atual
__LINE__ Número da linha atual
__DATE__ Data da compilação
__TIME__ Hora da compilação
__func__ Nome da função atual (C99)
__STDC__ Se está em conformidade com o padrão ANSI C
C
printf("Arquivo: %s\n", __FILE__);
printf("Linha: %d\n", __LINE__);
printf("Data: %s\n", __DATE__);
printf("Hora: %s\n", __TIME__);
printf("Funcao: %s\n", __func__);
TEXT
Arquivo: main.c
Linha: 5
Data: Jun 25 2026
Hora: 14:30:00
Funcao: main
💡 Dica: Macros de log personalizadas frequentemente combinam macros predefinidas para exibir nomes de arquivo, números de linha e outras informações de diagnóstico.

#undef para Cancelar uma Macro

#undef cancela uma definição de macro anterior:

C
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE

Após #undef, MAX_SIZE não é mais uma macro e pode ser redefinida ou usada como um identificador normal.

❓ Perguntas Frequentes

P: Por que definições de macro não terminam com ponto e vírgula? R: Macros são substituições de texto. Adicionar ponto e vírgula faz com que ele se torne parte do texto substituído, o que pode causar erros de sintaxe em contextos como if-else.

P: Macro de função ou função inline — qual devo usar? R: Funções inline têm verificação de tipo, avaliam argumentos apenas uma vez e são mais fáceis de depurar. Macros de função são flexíveis mas inseguras. Prefira funções inline; use macros apenas para casos especiais como operações genéricas por tipo (ex: SWAP).

P: Há diferença entre #ifdef e #if defined? R: Funcionalmente idênticas, mas #if defined é mais flexível — você pode combinar múltiplas condições: #if defined(A) && defined(B). #ifdef só pode verificar uma única macro.

P: Qual a diferença entre compilação condicional e comandos if? R: Compilação condicional poda código na etapa de pré-processamento — ramos não selecionados nunca entram na compilação. Com comandos if, todos os ramos são compilados; apenas o caminho em tempo de execução difere. Compilação condicional pode excluir código que não compilaria (ex: cabeçalhos específicos de plataforma).

📖 Resumo

📝 Exercícios

  1. Defina duas versões de uma macro SQUARE — uma com parênteses e outra sem. Teste ambas com SQUARE(2+3) e observe a diferença nos resultados
  2. Use #ifdef DEBUG para implementar uma macro de log LOG(fmt, ...) que exibe o nome do arquivo e número da linha em modo DEBUG e não produz saída caso contrário
  3. Use compilação condicional para implementar impressão multiplataforma: imprima "Plataforma Windows" no Windows, "Plataforma Linux" no Linux, usando #if defined(_WIN32) para detectar a plataforma
100%