O pré-processador
O pré-processador é uma etapa fundamental da compilação em C, responsável por manipular o código-fonte antes da compilação propriamente dita. Esta aula aborda as diretivas mais comuns (#include, #define), o funcionamento do pré-processador e os estágios de compilação, com exemplos práticos.
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:
- Pré-processamento: tratamento de diretivas, remoção de comentários, expansão de macros.
- Compilação: tradução do código pré-processado para assembly (ou código objeto).
- Montagem: conversão do assembly para código de máquina (objeto).
- 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
#definepara constantes numéricas, mas prefiraconstquando possível para maior segurança de tipo. - Evite macros com efeitos colaterais (ex.:
#define INC(x) x++). - Use
#includecom 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
- cppreference: Pré-processador C
- GNU CPP Manual
- TutorialsPoint: C Preprocessors
- IBM: C Preprocessor Directives
- Wikipedia: C preprocessor
Exercícios
-
Crie uma macro
#define SOMA(a,b)que retorna a soma de dois números. Teste comSOMA(5,3).✓ Resposta:#define SOMA(a,b) ((a)+(b)) int resultado = SOMA(5,3); // resultado = 8 -
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. -
Explique por que a macro
#define QUADRADO(x) x*xestá incorreta. Dê um exemplo.✓ Resposta: A macroQUADRADO(3+2)expande para3+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)). -
Use o pré-processador para criar uma compilação condicional que imprima "DEBUG" se a macro
DEBUGestiver definida.✓ Resposta:#include <stdio.h> #define DEBUG int main() { #ifdef DEBUG printf("DEBUG\n"); #endif return 0; } -
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.