Programar em C/Pré-processador
O pré-processador
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ção
#include
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>.
#define
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).
#undef
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 #ifndef
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.
#if
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.
#else
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
#elif
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 diretivas
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ção
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 ) );
}