Visão Geral e Fundamentos
Neste laboratório prático, exploraremos o funcionamento interno do utilitário cpp no ecossistema Ubuntu, detalhando as operações essenciais executadas antes da compilação propriamente dita. Abordaremos desde a expansão estrutural de macros e diretivas condicionais até a resolução customizada de cabeçalhos, fornecendo ferramentas analíticas indispensáveis para depurar pipelines de compilação em C/C++.
Preparação do Ambiente e Inspeção de Versão do utilitário cpp
Para assegurar a disponibilidade da suíte de ferramentas de compilação no sistema, iniciamos sincronizando os repositórios locais e instalando a distribuição do GCC via apt-get update -qq && apt-get install -y gcc.

Em seguida, validamos o registro do binário no PATH do sistema invocando which cpp.
A resposta /usr/bin/cpp evidencia que o executável padrão do pré-processador encontra-se devidamente instalado e acessível em um dos diretórios de sistema convencionais.

Ao emitir cpp --version, inspecionamos os metadados de build e a versão do GNU C Preprocessor embarcada na máquina.
O retorno confirma a versão 13.3.0 pronta para operação no Ubuntu.
⚙️ [Opções Fundamentais]
-D name : Declara explicitamente uma macro via linha de comando, útil para ramificações condicionais ou injeção de constantes.-U name : Anula ou desativa uma definição de macro previamente estabelecida ou nativa da arquitetura.-I dir : Adiciona um caminho personalizado à lista de diretórios consultados na resolução de diretivas #include.-dM : Gera um inventário exaustivo de todas as macros ativas e pré-definidas durante a execução.-dD : Mantém as declarações de #define visíveis junto ao código fonte pré-processado final.-P : Oprime a emissão de anotações de sincronização de linhas (#line) na saída de texto gerada.-v : Exibe o diagnóstico detalhado do processamento, expondo a ordem exata de busca de cabeçalhos no sistema.

Avaliação de Expansão de Macros e Substituição de Código
Para isolar os artefatos de teste, provisionamos um diretório dedicado e entramos nele por meio de mkdir -p ~/cpp_practice && cd ~/cpp_practice.

Construímos um arquivo de teste contendo tanto uma constante simbólica quanto uma macro parametrizada de cálculo através da instrução printf '#define BUFFER_SIZE 1024\n#define SQUARE(x) ((x) * (x))\nint limit = BUFFER_SIZE;\nint area = SQUARE(5);\n' > macro_test.c.

Processamos o arquivo de entrada gerando a saída intermediária correspondente ao executar cpp macro_test.c output.i.
As diretivas declaradas são convertidas e o resultado textual expandido é gravado diretamente em output.i.

Filtrando os marcadores estruturais do compilador através de cat output.i | grep -v '^#', examinamos apenas o código-fonte resultante.
Fica evidente que BUFFER_SIZE assumiu o literal 1024, enquanto a chamada SQUARE(5) desdobrou-se perfeitamente na expressão matemática ((5) * (5)).

Controle de Fluxo Condicional e Injeção de Macros (-D e -U)
Retornamos ao ambiente de trabalho principal executando cd ~/cpp_practice para conduzir os testes de compilação condicional.

Criamos um cenário representativo de build alternado entre desenvolvimento e produção emitindo printf '#ifdef DEBUG\nconst char *status = "DEBUG_ENABLED";\n#else\nconst char *status = "PRODUCTION";\n#endif\n' > condition_test.c.

Ao rodar cpp -DDEBUG -P condition_test.c, injetamos dinamicamente a definição DEBUG a partir do terminal, suprimindo marcadores com a flag -P.
A cláusula #ifdef avalia como verdadeira e seleciona unicamente a linha const char *status = "DEBUG_ENABLED";.

Em contrapartida, com o comando cpp -UDEBUG -P condition_test.c, forçamos a desativação da macro.
A ausência de DEBUG aciona o ramo correspondente ao bloco #else, fazendo com que a saída reflita com exatidão a declaração const char *status = "PRODUCTION";.

Resolução de Caminhos de Include e Limpeza de Metadados (-I e -P)
Garantimos a permanência no diretório de projeto por meio do comando cd ~/cpp_practice.

Para simular uma estrutura de diretórios modular típica de projetos de software, organizamos uma pasta isolada executando mkdir -p custom_headers.

Armazenamos a declaração de versão da aplicação dentro do novo diretório executando echo '#define APP_VERSION "1.0.0"' > custom_headers/version.h.

Criamos o módulo principal que consome esse cabeçalho externo por meio de printf '#include "version.h"\nconst char *ver = APP_VERSION;\n' > main_app.c.

Executamos o pré-processamento integrando o caminho de busca e silenciando diretivas de linha via cpp -I./custom_headers -P main_app.c.
O argumento -I orienta o motor a localizar version.h dentro da árvore ./custom_headers, enquanto a flag -P elimina poluições sintáticas residuais, entregando uma atribuição perfeitamente limpa: const char *ver = "1.0.0";.

Compreender a atuação do comando cpp no Linux desmistifica a primeira etapa da cadeia de compilação em C e C++. Essa visibilidade analítica sobre a expansão de código e a resolução de diretivas acelera significativamente o diagnóstico de erros sutis e a otimização de scripts de automação de compilação.