Programar em C/Pré-processador

O pré-processadorEditar

O pré-processador C é um programa que examina o programa fonte escrito em C e executa certas modificações nele, baseado nas diretivas de compilação (ou diretivas do pré-processador). As diretivas de compilação são comandos que não são compilados, sendo dirigidos ao pré-processador, executado pelo compilador antes da execução do processo de compilação propriamente dito.

Portanto, o pré-processador modifica o programa fonte, que ainda não estaria pronto para ser entregue ao compilador. Todas as diretivas de compilação são iniciadas pelo caractere # (sharp). As diretivas podem ser colocadas em qualquer parte do programa, mas não podem ser colocadas na mesma linha que outra diretiva ou instrução.

As principais diretivas de compilação são:

  • #include
  • #define
  • #undef
  • #ifdef
  • #ifndef
  • #if
  • #else
  • #elif
  • #endif

Diretivas de compilaçãoEditar

#includeEditar

A diretiva #include diz ao pré-processador para incluir naquele ponto um arquivo especificado. Sua sintaxe é:

#include "nome_do_arquivo"

ou

#include <nome_do_arquivo>

A diferença entre se usar "" e <> é somente a ordem de procura nos diretórios pelo arquivo especificado. Se você quiser informar o nome do arquivo com o caminho completo, ou se o arquivo estiver no diretório de trabalho, use "arquivo". Se o arquivo estiver nos caminhos de procura pré-especificados do compilador, isto é, se ele for um arquivo do próprio sistema (como é o caso de arquivos como stdio.h, string.h, etc...), use <arquivo>.

#defineEditar

A diretiva #define tem duas utilidades. Uma delas é apenas definir um símbolo que pode ser testado mais tarde. Outra é definir uma constante ou ainda uma macro com parâmetros. As três maneiras de usar a diretiva são:

#define nome_do_símbolo
#define nome_da_constante valor_da_constante
#define nome_da_macro(parâmetros) expressão_de_substituição
  • Toda vez que o pré-processador encontrar nome_da_constante no código a ser compilado, ele deve substituí-lo por valor_da_constante.
  • Toda vez que o pré-processador encontrar nome_da_macro(parâmetros), ele deve substituir por expressão_de_substituição, também substituindo os parâmetros encontrados na expressão de substituição; funciona mais ou menos como uma função. Veja o exemplo para entender melhor.

Exemplo 1:

#include <stdio.h>

#define PI      3.1416
#define VERSAO  "2.02"

int main ()
{
   printf ("Programa versão %s\n", VERSAO);
   printf ("O numero pi vale: %f\n", PI);

   return 0;
}

Exemplo 2:

#define max(A, B) ((A > B) ? (A) : (B))
#define min(A, B) ((A < B) ? (A) : (B))
...
x = max(i, j);
y = min(t, r);

Aqui, a linha de código: x = max(i, j); será substituída pela linha: x = ((i) > (j) ? (i) : (j));. Ou seja, atribuiremos a x o maior valor entre i ou j.

Quando você utiliza a diretiva #define, nunca deve haver espaços em branco no identificador (o nome da macro). Por exemplo, a macro #define PRINT (i) printf(" %d \n", i) não funcionará corretamente porque existe um espaço em branco entre PRINT e (i).

#undefEditar

A diretiva #undef tem a seguinte forma geral:

#undef nome_da_macro

Ela faz com que a macro que a segue seja apagada da tabela interna que guarda as macros. O compilador passa a partir deste ponto a não conhecer mais esta macro.

#ifdef e #ifndefEditar

O pré-processador também tem estruturas condicionais. No entanto, como as diretivas são processadas antes de tudo, só podemos usar como condições expressões que envolvam constantes e símbolos do pré-processador. A estrutura ifdef é a mais simples delas:

#ifdef nome_do_símbolo
  código
  ...
#endif

O código entre as duas diretivas só será compilado se o símbolo (ou constante) nome_do_símbolo já tiver sido definido. Há também a estrutura ifndef, que executa o código se o símbolo não tiver sido definido.

Lembre que o símbolo deve ter sido definido através da diretiva #define.

#ifEditar

A diretiva #if tem a seguinte forma geral:

#if expressão
  código
  ...
#endif 

A sequência de declarações será compilada apenas se a expressão fornecida for verdadeira. É muito importante ressaltar que a expressão fornecida não pode conter nenhuma variável, apenas valores constantes e símbolos do pré-processador.

#elseEditar

A diretiva #else funciona como na estrutura de bloco if (condição) {...} else {...}:

#if expressão  /* ou #ifndef expressão */
  código /* será executado se a expressão for verdadeira */
#else
  código /* será executado se a expressão for falsa */
#endif 

Um exemplo:

#define WINDOWS
...
/* código */
...
#ifdef WINDOWS
#define CABECALHO "windows_io.h"
#else
#define CABECALHO "unix_io.h"
#endif
#include CABECALHO

#elifEditar

A diretiva #elif serve para implementar uma estrutura do tipo if (condição) {...} else if (condição) {...}. Sua forma geral é:

#if expressão_1 
  código
#elif expressão_2 
  código
#elif expressão_3 
  código
. 
. 
. 
#elif expressão_n 
  código
#endif 

Podemos também misturar diretivas #elif com #else; obviamente, só devemos usar uma diretiva #else e ela deve ser a última (antes de #endif).

Usos comuns das diretivasEditar

Um uso muito comum das diretivas de compilação é em arquivos-cabeçalho, que só precisam/devem ser incluídos uma vez. Muitas vezes incluímos indiretamente um arquivo várias vezes, pois muitos cabeçalhos dependem de outros cabeçalhos. Para evitar problemas, costuma-se envolver o arquivo inteiro com um bloco condicional que só será compilado se o arquivo já não tiver incluído. Para isso usamos um símbolo baseado no nome do arquivo. Por exemplo, se nosso arquivo se chama "cabecalho.h", é comum usar um símbolo com o nome CABECALHO_H:

#ifndef CABECALHO_H
#define CABECALHO_H
.
.
.
#endif

Se o arquivo ainda não tiver sido incluído, ao chegar na primeira linha do arquivo, o pré-processador não encontrará o símbolo CABECALHO_H, e continuará a ler o arquivo, o que lhe fará definir o símbolo. Se tentarmos incluir novamente o arquivo, o pré-processador pulará todo o conteúdo pois o símbolo já foi definido.

ConcatenaçãoEditar

O pré-processador C oferece duas possibilidades para manipular uma cadeia de caracteres .
A primeira é usando o operador # que permite substituir a grafia de um parâmetro .

  
#include<stdio.h>

int main (void)
{


/* mad equivale a "mad"  */
#define String(mad)  #mad

printf ( String( Estou aqui ) "\n" );

}

A segunda é usando o operador ## que serve para concatenar vários parâmetros .
Ex: ban##ana é igual a banana .

 
#include<stdio.h>

int main (void)
{

int teste = 1000 ;

#define CONCAT(x, y) x##y

/* igual a "tes" + "te" */
printf (" %i \n", CONCAT ( tes, te ) );

}