Menu

O pré-processador do C: #include, #define e o que roda antes da compilação

O pré-processador edita o texto do seu código antes de o compilador sequer vê-lo - colando cabeçalhos, substituindo macros e ligando ou desligando trechos de código. Aqui está toda a família de diretivas e como olhar a saída dela.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Todo arquivo C que você já compilou começou com uma linha como #include <stdio.h>, e essa linha não é C. É uma instrução para um programa separado - o pré-processador - que roda primeiro, reescreve o texto do seu código e entrega o resultado ao compilador.

Entender essa divisão em duas etapas explica muito do comportamento do C: por que cabeçalhos são colados em vez de importados, por que uma macro ruim produz um erro numa linha que parece perfeitamente correta, e por que o mesmo arquivo-fonte pode compilar para programas diferentes em sistemas diferentes.

O que realmente acontece antes da compilação

Compilar um arquivo .c não é um passo só. Grosso modo, são quatro:

  1. Pré-processamento - obedecer a cada linha que começa com #, produzindo um grande texto-fonte expandido (uma unidade de tradução).
  2. Compilação - transformar esse texto em assembly e depois num arquivo-objeto.
  3. Montagem - produzir código de máquina.
  4. Ligação - juntar os arquivos-objeto com as bibliotecas num executável.

O pré-processador trabalha puramente sobre texto. Ele não sabe o que é uma variável, o que é um tipo, nem se as suas chaves estão balanceadas. Ele vê caracteres e tokens, substitui alguns por outros e segue em frente. Tudo o que há de estranho nas macros decorre desse único fato.

Quando o compilador lê este programa, não existe nenhum GREETING em lugar nenhum. O pré-processador já o substituiu pelo literal "Ola, vindo de uma macro", exatamente como se você o tivesse digitado.

A família de diretivas

Toda diretiva de pré-processador começa com # como primeiro caractere não branco da linha. Não há ponto e vírgula, e a diretiva termina no fim da linha, a menos que você a continue com uma barra invertida.

DiretivaO que faz
#includeCola o conteúdo de outro arquivo
#defineDefine uma macro (uma substituição de texto)
#undefRemove a definição de uma macro
#ifdef, #ifndefMantém o código seguinte só se uma macro estiver (ou não) definida
#if, #elif, #else, #endifMantém código conforme uma expressão constante
#errorInterrompe a compilação com uma mensagem
#pragmaInstrução específica do compilador, como #pragma once
#lineMuda o número de linha reportado (raro)

Três delas têm páginas próprias: macros cobre o #define a fundo, compilação condicional cobre a família #if e arquivos de cabeçalho cobre como o #include é usado para estruturar um programa com vários arquivos.

#include: colchetes angulares vs aspas

O #include faz exatamente uma coisa: substitui a própria linha pelo conteúdo inteiro do arquivo indicado. Esse arquivo incluído é, por sua vez, pré-processado, então as linhas #include dele também se expandem.

#include <stdio.h>     /* procura nos diretorios de include do sistema */
#include "config.h"    /* procura primeiro no diretorio deste arquivo */

A diferença é a ordem de busca:

  • <colchetes angulares> procuram nos diretórios padrão de include do compilador - /usr/include, as pastas da própria toolchain, mais qualquer coisa que você acrescente com -I. Isso é para cabeçalhos de biblioteca.
  • "aspas" procuram primeiro no diretório que contém o arquivo que está fazendo a inclusão e depois recorrem à mesma lista dos colchetes angulares. Isso é para cabeçalhos que você escreveu.

As duas formas funcionam para qualquer tipo de cabeçalho na maioria dos compiladores, mas a convenção carrega significado: colchetes angulares dizem "este cabeçalho é de outra pessoa", aspas dizem "este é meu". Trocar os dois é como um projeto acaba incluindo um cabeçalho de sistema desatualizado em vez do seu próprio.

Como a inclusão é uma colagem textual, incluir o mesmo cabeçalho duas vezes o cola duas vezes - e é por isso que cabeçalhos precisam de guardas de inclusão. Essa é a primeira coisa que a página de arquivos de cabeçalho resolve.

#define: substituição de texto, nada além disso

#define NOME substituicao diz ao pré-processador: daqui em diante, onde quer que apareça o token NOME, coloque substituicao no lugar.

Repare no que não está acontecendo aqui. MAX_USERS não tem tipo. Não é armazenado em lugar nenhum. Não pode ser inspecionado num depurador. É uma regra de localizar-e-substituir, e depois do pré-processamento o programa contém literalmente printf("%s permite %d usuarios\n", "Coddy", 100);.

Isso também significa que a substituição é cega. Isto compila, e faz algo surpreendente:

#define SIZE 5 + 1

int arr[SIZE];          /* ok: int arr[5 + 1]; */
int total = SIZE * 2;   /* 5 + 1 * 2 == 7, nao 12 */

A solução - parênteses em torno de tudo - é a regra central da página de macros, junto com as macros que recebem argumentos.

Um #define sem texto de substituição define o nome como "presente, porém vazio". Isso é inútil como substituição e essencial como sinalizador:

#define DEBUG          /* definido, expande para nada */

O código pode então perguntar se DEBUG existe com #ifdef.

Vendo a saída do pré-processador

A melhor maneira de construir intuição é olhar o que o pré-processador de fato produziu. gcc -E para depois do pré-processamento e imprime o resultado:

gcc -E hello.c

Para um arquivo que inclui <stdio.h>, isso dá de 700 a 30.000 linhas dependendo do seu sistema - quase tudo o conteúdo do próprio cabeçalho. Para ver só a sua parte, pegue o final:

gcc -E hello.c | tail -20

Teste com um arquivo como este:

#define SQUARE(x) ((x) * (x))
#define LIMIT 10

int main(void) {
    int n = SQUARE(LIMIT);
    return n;
}

O final da saída mostra:

int main(void) {
    int n = ((10) * (10));
    return n;
}

Todas as macros sumiram; só resta o texto substituído. Quando uma macro se comporta mal, esse comando diz por quê em segundos, e é muito melhor do que adivinhar. Dois companheiros úteis: gcc -dM -E - < /dev/null lista todas as macros que o seu compilador predefine, e gcc -E -P arquivo.c omite o ruído dos marcadores de linha.

Por que os erros apontam para a linha errada

Como o compilador enxerga o texto expandido, um erro dentro de uma macro é reportado onde a macro foi usada, não onde ela foi escrita:

#define HALF(x) (x / 2

int main(void) {
    int y = HALF(8);   /* erro reportado aqui */
    return 0;
}

O parêntese que falta está na linha do #define, mas o compilador reclama da linha que contém HALF(8), muitas vezes com uma mensagem sobre um token inesperado que não faz sentido nenhum no contexto. Quando um erro parece impossível, expanda o arquivo com gcc -E e leia a linha de verdade.

Compiladores modernos ajudam: GCC e clang imprimem uma nota "in expansion of macro" apontando de volta para a definição. Compile com -Wall -Wextra para que você realmente veja essas notas.

Macros predefinidas

O próprio pré-processador fornece algumas macros. Estas são genuinamente úteis para diagnóstico:

__FILE__ e __LINE__ expandem para o nome do arquivo e o número da linha atuais, que é como macros de asserção e de log reportam onde algo deu errado. (__func__ é um pouco diferente - é um identificador de verdade fornecido pelo compilador, não uma macro de pré-processador, mas é usado da mesma forma.)

Os compiladores também predefinem macros de plataforma como __linux__, _WIN32 e __APPLE__. Código que precisa diferir por sistema testa essas macros com #ifdef, que é o assunto de compilação condicional.

O que levar daqui

O pré-processador é pequeno e é burro, e as duas coisas são de propósito. Ele lhe dá três poderes - trazer um arquivo, substituir texto e ligar ou desligar código - e nenhuma verificação de tipos para acompanhá-los.

Essa troca é a razão de o conselho padrão ser recorrer primeiro aos recursos da linguagem: use const int ou um enum em vez de #define para constantes quando puder, e uma função de verdade em vez de uma macro parecida com função. Onde o pré-processador é genuinamente a ferramenta certa - cabeçalhos, chaves de portabilidade, configuração em tempo de compilação - ele é insubstituível.

A seguir, macros leva o #define a sério: argumentos, as regras dos parênteses e as armadilhas que vêm com substituir texto dentro de um código que você não escreveu.

Perguntas frequentes

O que é o pré-processador em C?

É uma etapa de processamento de texto que roda antes da compilação. Ela obedece às linhas que começam com # - colando o conteúdo dos arquivos de cabeçalho no lugar do #include, substituindo as macros definidas com #define e apagando ou mantendo código conforme #if/#ifdef. O compilador só enxerga o resultado, nunca o seu arquivo original.

Qual a diferença entre #include <stdio.h> e #include "meuarquivo.h"?

Os colchetes angulares procuram nos diretórios de include do sistema, que é onde ficam os cabeçalhos da biblioteca padrão. As aspas procuram primeiro no diretório do arquivo atual e depois recorrem aos caminhos do sistema. Use colchetes angulares para cabeçalhos de biblioteca e aspas para os cabeçalhos que você escreveu.

Como posso ver o que o pré-processador produziu?

Rode gcc -E arquivo.c para parar depois do pré-processamento e imprimir o código expandido. Num arquivo que inclui <stdio.h> a saída tem milhares de linhas, então filtre: gcc -E arquivo.c | tail -30 mostra só o seu próprio código, com todas as macros já substituídas.

#include é uma instrução do C?

Não. As diretivas não fazem parte da linguagem C propriamente dita - elas têm sintaxe própria baseada em linhas, não levam ponto e vírgula e já sumiram quando o compilador analisa o seu programa. É por isso que um erro numa macro aparece como uma mensagem confusa numa linha que parece perfeita.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR