A compilação de qualquer programa C
começa com um pré-processamento
que cuida das linhas do programa que começam com #
.
Esse pré-processamento é realizado por um módulo do compilador
conhecido como pré-processador,
ou pré-compilador.
A ideia de um programa que tem dois níveis lógicos
,
com uma sintaxe para uma parte das linhas de código
e outra sintaxe para as demais linhas,
pode parecer deselegante.
Mas do ponto de vista prático, ela é útil e poderosa.
Este sítio usa apenas duas
diretivas
(= directives)
de pré-processamento:
#define e #include.
A diretiva define define uma abreviatura — conhecida como macro — para um trecho de código. Por exemplo, a linha
#define MAX 1000
define MAX
como
abreviatura de 1000
.
A sequência de caracteres que começa depois de MAX
e vai ⚠ até o fim da linha
é o significado da abreviatura,
ou seja, o valor da macro.
(Se você escrever um ;
no fim da linha,
esse caractere fará parte da macro.)
Exemplo 1. O programa
#define MAX 1000 int main (void) { int v[MAX]; for (int i = 0; i < MAX; ++i) { . . . } return EXIT_SUCCESS; }
é transformado pelo pré-processador em
int main (void) { int v[1000]; for (int i = 0; i < 1000; ++i) { . . . } return EXIT_SUCCESS; }
Exemplo 2. Tal como uma função, uma macro pode ter parâmetros. Assim, o trecho de programa
#define troca (A, B) {int t = A; A = B; B = t;} for (int i = 0; i < 100; ++i) { int k = i; for (int j = i+1; j <= 100; ++j) if (a[j] < a[k]) k = j; troca (a[i], a[k]); }
é transformado pelo pré-processador em
for (int i = 0; i < 100; ++i) { int k = i; for (int j = i+1; j <= 100; ++j) if (a[j] < a[k]) k = j; {int t = a[i]; a[i] = a[k]; a[k] = t;} }
A diretiva include acrescenta ao seu programa o conteúdo do arquivo especificado.
Exemplo 1. Suponha que um arquivo aaa.txt no diretório corrente tem o seguinte conteúdo:
int GLOB = 8; // variável global int func (int e); // protótipo de função
Então o programa
#include "aaa.txt"
int main (void) {
while (GLOB <= 64) {
int y = func (5);
printf ("%d\n", y);
GLOB *= 2;
}
return EXIT_SUCCESS;
}
int func (int i) { // calcula GLOB^i
. . .
}
será transformado pelo pré-processador em
int GLOB = 8;
int func (int e);
int main (void) {
while (GLOB <= 64) {
int y = func (5);
printf ("%d\n", y);
GLOB *= 2;
}
return EXIT_SUCCESS;
}
int func (int i) { // calcula GLOB^i
. . .
}
Exemplo 2.
Se o nome do arquivo a ser incluído
estiver embrulhado em <
e >
(e não em aspas duplas),
o pré-processador irá procurá-lo num diretório apropriado do sistema
(usualmente no diretório /usr/include/)
e não no diretório corrente:
#include <aaa.txt> int main (void) { . . . }
Isso é usado, em geral, para incluir interfaces de bibliotecas padrão no seu programa.
Exemplo 3. O arquivo a ser incluído pode conter outros #include e #define. Nesse caso, a expansão das diretivas é recursiva. Suponha, por exemplo, que o arquivo aaa.txt no diretório corrente consiste no seguinte:
#define PI 3.14159 #include <math.h>
Então o programa
#include "aaa.txt" int main (void) { double y = sin (PI/4); printf ("%f\n", y); return EXIT_SUCCESS; }
será transformado pelo pré-processador em
. . . . . . int main (void) { double y = sin (3.14159/4); printf ("%f\n", y); return EXIT_SUCCESS; }
onde as linhas . . .
estão no lugar do conteúdo do arquivo math.h.
O resultado do pré-processamento de um programa
é uma versão limpa
do programa,
sem diretivas de pré-processamento.
O programador não precisa colocar as mãos nessa versão pré-processada,
pois ela será automaticamente submetida
ao módulo nobre
do compilador.
Entretanto, se tiver curiosidade,
o programador pode examinar a versão pré-processada:
a linha de comando
~$ gcc -E xxx.c > yyy.c
(note a opção -E do compilador) submete o programa original xxx.c ao compilador e grava a versão pré-processada do programa no arquivo yyy.c.