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:
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
#include <stdio.h>
#include "myheader.h"
< >busca nos diretórios do sistema" "busca primeiro no diretório atual, depois nos diretórios do sistema
" " 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:
#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);
Macros de Função
Macros com parâmetros parecem funções, mas são substituições de texto:
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int s = SQUARE(5);
int m = MAX(10, 20);
25
20
Armadilhas de Macros e Parênteses
Armadilha 1: Falta de Parênteses Causa Erros de Precedência
#define DOUBLE(x) x + x
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:
#define DOUBLE(x) ((x) + (x))
Armadilha 2: Parâmetros com Efeitos Colaterais
#define SQUARE(x) ((x) * (x))
int n = 3;
int s = SQUARE(n++);
Após a expansão isso se torna ((n++) * (n++)), incrementando n duas vezes — comportamento indefinido.
i++, func()) como argumentos de macro. Use funções inline ou funções reais em vez disso.
Armadilha 3: Macros em Ramos if-else
#define CHECK(cond) if (cond) printf("yes\n")
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):
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)
Exemplo
#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;
}
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
#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);
Plataforma: Linux
#ifdef / #ifndef
#ifdef verifica se uma macro está definida; #ifndef verifica se não está definida.
#define DEBUG
#ifdef DEBUG
printf("Modo debug: x=%d\n", x);
#endif
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
#ifdef DEBUG é equivalente a #if defined(DEBUG), e #ifndef é equivalente a #if !defined(...).
Chave de Depuração
#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:
gcc -DDEBUG -o myapp myapp.c
Exemplo
#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;
}
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 |
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__);
Arquivo: main.c
Linha: 5
Data: Jun 25 2026
Hora: 14:30:00
Funcao: main
#undef para Cancelar uma Macro
#undef cancela uma definição de macro anterior:
#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).#ifdefsó 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
#includeinsere o conteúdo do arquivo de cabeçalho no fonte;< >busca nos diretórios do sistema," "busca no diretório atual#definedefine macros de objeto (substituição de constante) e macros de função (substituição de texto com parâmetros)- Parâmetros de macro devem ser colocados entre parênteses, e a expressão inteira da macro também, para evitar armadilhas de precedência
- Nunca passe expressões com efeitos colaterais como argumentos de macro
- Compilação condicional com
#if/#ifdef/#ifndefpermite suporte multiplataforma, chaves de depuração e remoção de recursos - Macros predefinidas como
__FILE__,__LINE__,__DATE__são úteis para log e depuração
📝 Exercícios
- 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 - Use
#ifdef DEBUGpara implementar uma macro de logLOG(fmt, ...)que exibe o nome do arquivo e número da linha em modo DEBUG e não produz saída caso contrário - 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



