O pré-processador é uma ferramenta poderosa que opera antes do compilador, processando diretivas que começam com #. Ele realiza tarefas como inclusão de arquivos, definição de macros e compilação condicional, permitindo maior flexibilidade e reutilização de código. Compreender seu funcionamento é essencial para escrever código C eficiente e portável.

#include

A diretiva #include instrui o pré-processador a inserir o conteúdo de um arquivo no local da diretiva. É usada principalmente para incluir cabeçalhos de bibliotecas padrão ou arquivos de definição próprios.

Existem duas formas de uso:

  • #include <arquivo.h>: procura o arquivo nos diretórios de inclusão padrão do sistema (como /usr/include).
  • #include "arquivo.h": procura primeiro no diretório atual do código-fonte e, se não encontrado, nos diretórios padrão.

Exemplo:

#include <stdio.h>   // inclui a biblioteca padrão de entrada/saída
#include "meu_header.h" // inclui um arquivo local

O pré-processador substitui a linha #include pelo conteúdo completo do arquivo, o que pode aumentar significativamente o tamanho do código intermediário.

#define

A diretiva #define define macros, que são substituições de texto realizadas pelo pré-processador. Pode ser usada para definir constantes ou macros com parâmetros (semelhantes a funções).

Definição de constante:

#define PI 3.14159

Todo uso de PI no código é substituído por 3.14159.

Macro com parâmetros:

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

O uso de parênteses é crucial para evitar problemas de precedência. Exemplo:

int a = QUADRADO(3+2); // expande para ((3+2)*(3+2)) = 25

Sem os parênteses, 3+2*3+2 resultaria em 11.

Como funciona

O pré-processador lê o código-fonte e executa as diretivas linha por linha. Ele não conhece a sintaxe da linguagem C; trata o texto como uma sequência de tokens. As principais operações são:

  • Substituição de texto (macros).
  • Inclusão de arquivos.
  • Compilação condicional (#if, #ifdef, #ifndef, #else, #elif, #endif).
  • Remoção de comentários.
  • Expansão de macros aninhadas.

O resultado é um código-fonte expandido, sem diretivas, que é passado ao compilador.

Estágios

A compilação de um programa C é dividida em várias etapas. O pré-processador corresponde ao primeiro estágio. Os estágios completos são:

  1. Pré-processamento: tratamento de diretivas, remoção de comentários, expansão de macros.
  2. Compilação: tradução do código pré-processado para assembly (ou código objeto).
  3. Montagem: conversão do assembly para código de máquina (objeto).
  4. Ligação (linkagem): combinação de múltiplos objetos em um executável.

Para visualizar a saída do pré-processador, use a flag -E do GCC:

gcc -E programa.c -o programa.i

O arquivo programa.i contém o código expandido.

Boas práticas

  • Use #define para constantes numéricas, mas prefira const quando possível para maior segurança de tipo.
  • Evite macros com efeitos colaterais (ex.: #define INC(x) x++).
  • Use #include com cabeçalhos padrão usando <> e cabeçalhos próprios com "".
  • Inclua guards de inclusão (#ifndef) em cabeçalhos para evitar duplicação.

Referências

Exercícios

  1. Crie uma macro #define SOMA(a,b) que retorna a soma de dois números. Teste com SOMA(5,3).

    ✓ Resposta:
    #define SOMA(a,b) ((a)+(b))
    int resultado = SOMA(5,3); // resultado = 8
  2. Qual a diferença entre #include <stdio.h> e #include "stdio.h"?

    ✓ Resposta: #include <stdio.h> procura apenas nos diretórios de inclusão padrão do sistema. #include "stdio.h" procura primeiro no diretório atual do código-fonte e, se não encontrar, nos diretórios padrão. Para cabeçalhos padrão, a forma com <> é preferível.
  3. Explique por que a macro #define QUADRADO(x) x*x está incorreta. Dê um exemplo.

    ✓ Resposta: A macro QUADRADO(3+2) expande para 3+2*3+2, que resulta em 11 (devido à precedência da multiplicação) em vez de 25. A forma correta é #define QUADRADO(x) ((x)*(x)).
  4. Use o pré-processador para criar uma compilação condicional que imprima "DEBUG" se a macro DEBUG estiver definida.

    ✓ Resposta:
    #include <stdio.h>
    #define DEBUG
    int main() {
    #ifdef DEBUG
        printf("DEBUG\n");
    #endif
        return 0;
    }
  5. Qual comando do GCC gera apenas a saída do pré-processador? Qual a extensão típica do arquivo gerado?

    ✓ Resposta: O comando é gcc -E programa.c -o programa.i. A extensão típica é .i.