Diferenças

Aqui você vê as diferenças entre duas revisões dessa página.

Link para esta página de comparações

Ambos lados da revisão anterior Revisão anterior
c:o_preprocessador_c [2023/08/15 14:53] – [Definição e uso de constantes] mazieroc:o_preprocessador_c [2023/08/15 14:53] (atual) – [Compilação condicional] maziero
Linha 1: Linha 1:
 +====== 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'':
 +
 +<code>
 +gcc -E arquivo.c
 +</code>
 +
 +===== Inclusão de arquivos =====
 +
 +O preprocessador é frequentemente usado para incluir arquivos externos em um código-fonte.
 +
 +Considere este exemplo:
 +
 +<code c escreva.h>
 +#ifndef __ESCREVA__
 +#define __ESCREVA__
 +
 +void escreva (char *msg) ;
 +
 +#endif
 +</code>
 +
 +<code c escreva.c>
 +#include <stdio.h>
 +
 +void escreva (char *msg)
 +{
 +  printf ("%s", msg) ;
 +}
 +</code>
 +
 +<code c hello.c>
 +#include "escreva.h"
 +
 +int main ()
 +{
 +  escreva ("Hello, world!\n") ;
 +  return (0) ;
 +}
 +</code>
 +
 +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'':
 +
 +<code c vetor.c>
 +#define VETSIZE 64
 +
 +int main ()
 +{
 +  int vetor[VETSIZE] ;
 +
 +  for (i = 0; i < VETSIZE; i++)
 +    vetor[i] = i ;
 +}
 +</code>
 +
 +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:
 +
 +<code C>
 +int vetor[64] ;
 +
 +for (i = 0; i < 64; i++)
 +  vetor[i] = 0 ;
 +</code>
 +
 +<note important>
 +Para evitar confusões entre variáveis da linguagem C e constantes do preprocessador, convencionou-se definir as constantes em MAIÚSCULAS.
 +</note>
 +
 +===== 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:
 +
 +<code c data.c>
 +#include <stdio.h>
 +
 +int main ()
 +{
 +  printf ("Este código foi compilado em %s\n", __DATE__) ;
 +}
 +</code>
 +
 +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:
 +
 +<code C debug.c>
 +#include <stdio.h>
 +
 +#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
 +  }
 +}
 +</code>
 +
 +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:
 +
 +<code>
 +gcc -DDEBUG debug.c
 +</code>
 +
 +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:
 +
 +<code c headers.h>
 +#ifndef _THIS_HEADER_FILE_
 +#define _THIS_HEADER_FILE_
 +...
 +#endif
 +</code>
 +
 +Da mesma forma, pode-se evitar redefinir constantes que já estejam definidas:
 +
 +<code c>
 +#ifndef NULL
 +#define NULL (void *) 0
 +#endif
 +</code>
 +
 +Além do ''ifdef'', existem outros operadores condicionais, como o ''if'' - ''elif'' - ''else'':
 +
 +<code c>
 +#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
 +</code>
 +
 +O operador ''defined'' permite testar se uma macro está definida:
 +
 +<code c>
 +#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
 +</code>
 +
 +<note tip>
 +Observe o uso das diretivas ''#warning'' e ''#error'' no programa acima.
 +</note>
 +
 +===== Macros com parâmetros =====
 +
 +O preprocessador é usado com frequência para construir macros, que são funções simples com parâmetros:
 +
 +<code c macro1.c>
 +#include <stdio.h>
 +
 +#define SQUARE(x) x*x
 +
 +int main ()
 +{
 +  printf ("O quadrado de 5 é %d\n", SQUARE(5)) ;
 +}
 +</code>
 +
 +O código acima, ao ser tratado pelo preprocessador, será transformado em:
 +
 +<code c>
 +printf ("O quadrado de 5 é %d\n", 5*5) ;
 +</code>
 +
 +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).
 +
 +<note important>
 +Tome **muito** cuidado ao definir macros com parâmetros, pois a substituição de texto pode levar a expressões erradas!
 +</note>
 +
 +O exemplo abaixo apresenta um erro dessa natureza:
 +
 +<code c>
 +#define SQUARE(x) x*x
 +...
 +printf ("O quadrado de 2+3 é %d\n", SQUARE(2+3)) ;
 +</code> 
 +
 +O preprocessador transformará essa expressão em:
 +
 +<code c>
 +printf ("O quadrado de 2+3 é %d\n", 2+3*2+3) ;
 +</code>
 +
 +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**:
 +
 +<code c macro2.c>
 +#include <stdio.h>
 +
 +#define SQUARE(x) (x)*(x)
 +
 +int main ()
 +{
 +  printf ("O quadrado de 5 é %d\n", SQUARE(2+3)) ;
 +}
 +</code>
 +
 +Que resulta na expressão correta:
 +
 +<code c>
 +printf ("O quadrado de 2+3 é %d\n", (2+3)*(2+3)) ;
 +</code>
 +
 +===== 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]].