====== O preprocessador C ====== {{ progc_preprocessor.mkv |Video desta aula}} A transformação de um programa C em um arquivo executável é um processo complexo, com várias etapas, sendo as mais importantes: * **preprocessamento**: tratamento das diretivas do preprocessador (''#include'', etc) * **compilação**: conversão de C para //assembly// * **tradução**: conversão de //assembly// para código de máquina (binário) * **ligação**: junção dos arquivos-objeto e bibliotecas em código de máquina para formar o arquivo executável Ele está ilustrado na figura a seguir, onde ''cpp'' corresponde ao preprocessamento, ''gcc'' à compilação, ''as'' à tradução e ''ld'' à ligação. {{ cpp.png?600 | Do fonte ao executável}} ===== Preprocessamento ===== O preprocessador C (CPP - //C PreProcessor//) é uma ferramenta de **substituição de texto** invocada automaticamente pelo compilador C/C++ no início do processo de compilação. Apesar de não fazer parte formal da sintaxe da linguagem C, seu uso é praticamente indispensável para a estruturação de programas C, mesmo os mais simples. Todos os comandos do preprocessador começam com o símbolo ''#'' no início de uma linha (podem haver espaços e tabs antes). A saída do preprocessador C, que será enviada ao compilador propriamente dito, pode ser obtida através do flags ''-E'': gcc -E arquivo.c ===== Inclusão de arquivos ===== O preprocessador é frequentemente usado para incluir arquivos externos em um código-fonte. Considere este exemplo: #ifndef __ESCREVA__ #define __ESCREVA__ void escreva (char *msg) ; #endif #include void escreva (char *msg) { printf ("%s", msg) ; } #include "escreva.h" int main () { escreva ("Hello, world!\n") ; return (0) ; } Neste exemplo, o preprocessador irá **substituir** cada linha ''#include ...'' pelo conteúdo do respectivo arquivo, gerando um único arquivo temporário que será entregue ao compilador C. Há uma diferença importante entre as duas formas de inclusão: * ''#include <...>'': o arquivo indicado será buscado nos diretórios default do compilador, geralmente ''/usr/include/*'' nos sistemas Unix. * ''#include "..."'': o arquivo indicado será buscado primeiro no diretório corrente (onde está o arquivo que está sendo compilado), e depois nos diretórios default do compilador. ===== Definição e uso de constantes ===== O preprocessador é frequentemente usado para a definição de constantes, através do comando ''#define'': #define VETSIZE 64 int main () { int vetor[VETSIZE] ; for (i = 0; i < VETSIZE; i++) vetor[i] = i ; } Após a definição, todas as ocorrências da string ''VETSIZE'' no arquivo serão substituídas pelo valor 64, **antes da compilação**, resultando no seguinte código-fonte: int vetor[64] ; for (i = 0; i < 64; i++) vetor[i] = 0 ; Para evitar confusões entre variáveis da linguagem C e constantes do preprocessador, convencionou-se definir as constantes em MAIÚSCULAS. ===== Constantes predefinidas ===== Algumas constantes são definidas previamente pelo sistema: * ''%%__DATE__%%'': data atual (formato "MMM DD YYYY") * ''%%__TIME__%%'': horário atual (formato "HH:MM:SS") * ''%%__FILE__%%'': nome do arquivo corrente. * ''%%__LINE__%%'': número da linha corrente do código-fonte. * ''%%__func__%%'': nome da função corrente. Um exemplo de uso: #include int main () { printf ("Este código foi compilado em %s\n", __DATE__) ; } Além destas, muitas outras constantes podem estar disponíveis, dependendo da plataforma: * [[https://gcc.gnu.org/onlinedocs/cpp/Predefined-Macros.html|Macros predefinidas no preprocessador GNU]] * [[https://sourceforge.net/p/predef/wiki/Home/|Pre-defined C/C++ Compiler Macros]] ===== Compilação condicional ===== Uma constante pode ser definida sem um valor específico, Neste caso ela funciona como como um //flag// verdadeiro/falso, que pode ser testado pelo preprocessador através de comandos específicos: #include #define VETSIZE 64 int main () { int i, vetor[VETSIZE] ; for (i = 0; i < VETSIZE; i++) { vetor[i] = i ; #ifdef DEBUG printf ("Valor de i: %d\n", i) ; #endif } } No exemplo acima, a linha do ''printf'' só estará presente no código enviado ao compilador se a constante ''DEBUG'' estiver definida. Constantes podem ser definidas no código-fonte usando o comando ''#define'' (como nos exemplos acima), mas também podem ser definidas na linha de comando, ao invocar o compilador: gcc -DDEBUG debug.c Um uso frequente da compilação condicional é a construção de //include guards//, ou seja código para evitar múltiplas inclusões do mesmo arquivo: #ifndef _THIS_HEADER_FILE_ #define _THIS_HEADER_FILE_ ... #endif Da mesma forma, pode-se evitar redefinir constantes que já estejam definidas: #ifndef NULL #define NULL (void *) 0 #endif Além do ''ifdef'', existem outros operadores condicionais, como o ''if'' - ''elif'' - ''else'': #if DEBUG_LEVEL > 5 // print all debug messages ... #elif DEBUG_LEVEL > 3 // print relevant debug messages ... #elif DEBUG_LEVEL > 1 // print prioritary debug messages ... #else // print no debug messages #endif O operador ''defined'' permite testar se uma macro está definida: #if defined (__arm__) // macro definida em sistemas ARM #warning "Generating code for ARM processor." // code for ARM processors ... #elif defined (__i386__) // idem, x86 #warning "Generating code for x86 processor." // code for Intel 32-bit processors ... #else // abort compilation #error "Unknown architecture, aborting." #endif Observe o uso das diretivas ''#warning'' e ''#error'' no programa acima. ===== Macros com parâmetros ===== O preprocessador é usado com frequência para construir macros, que são funções simples com parâmetros: #include #define SQUARE(x) x*x int main () { printf ("O quadrado de 5 é %d\n", SQUARE(5)) ; } O código acima, ao ser tratado pelo preprocessador, será transformado em: printf ("O quadrado de 5 é %d\n", 5*5) ; Observe que a macro ''SQUARE'' não computou o resultado de 5*5, apenas fez a substituição do parâmetro 5 pela expressão que ela define (5*5). Tome **muito** cuidado ao definir macros com parâmetros, pois a substituição de texto pode levar a expressões erradas! O exemplo abaixo apresenta um erro dessa natureza: #define SQUARE(x) x*x ... printf ("O quadrado de 2+3 é %d\n", SQUARE(2+3)) ; O preprocessador transformará essa expressão em: printf ("O quadrado de 2+3 é %d\n", 2+3*2+3) ; O resultado da expressão deveria ser 25 (o quadrado de 2+3) mas será 11, por causa da precedência entre os operadores aritméticos (que o preprocessador não trata). Para evitar esse erro, a macro deve ser declarada usando **parênteses**: #include #define SQUARE(x) (x)*(x) int main () { printf ("O quadrado de 5 é %d\n", SQUARE(2+3)) ; } Que resulta na expressão correta: printf ("O quadrado de 2+3 é %d\n", (2+3)*(2+3)) ; ===== Operações avançadas ===== O preprocessador C tem operações avançadas que vão muito além do escopo desta breve introdução. Para uma visão mais completa e profunda consulte este documento: [[https://gcc.gnu.org/onlinedocs/cpp/index.html|The C Preprocessor]].