sábado, julho 09, 2011

Aula 8 - Diretivas de Compilação


As Diretivas de Compilação

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. As diretivas de compilação são comandos que não são compilados, sendo dirigidos ao pré-processador, que é executado pelo compilador antes da execução do processo de compilação propriamente dito.
Portanto, o pré-processador modifica o programa fonte, entregando para o compilador um programa modificado. Todas as diretivas de compilação são iniciadas pelo caracter #. As diretivas podem ser colocadas em qualquer parte do programa. Já vimos, e usamos muito, a diretiva #include. Sabemos que ela não gera código mas diz ao compilador que ele deve incluir um arquivo externo na hora da compilação. As diretivas do C são identificadas por começarem por #. As diretivas que estudaremos são definidas pelo padrão ANSI: 
#if  
#else  
#include   
#ifdef  
#elif  
#define   
#ifndef  
#endif  
#undef  
Procuraremos ser breves em suas descrições... 

A Diretiva include

A diretiva #include já foi usada durante o nosso curso diversas vezes. Ela diz ao compilador para incluir, na hora da compilação, um arquivo especificado. Sua forma geral é:
#include "nome_do_arquivo"
ou
#include


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 " ". 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 < >.
Observe que não há ponto e vírgula após a diretiva de compilação. Esta é uma característica importante de todas as diretivas de compilação e não somente da diretiva #include

As Diretivas define e undef

A diretiva #define tem a seguinte forma geral:
#define nome_da_macro sequência_de_caracteres
Quando você usa esta diretiva, você está dizendo ao compilador para que, toda vez que ele encontrar o nome_da_macro no programa a ser compilado, ele deve substituí-lo pela sequência_de_caracteres fornecida. Isto é muito útil para deixar o programa mais geral. Veja um exemplo:
#include 
#define PI 3.1416
#define VERSAO "2.02"
int main ()
{
      printf ("Programa versao %s",VERSAO);
      printf ("O numero pi vale: %f",PI);
      return 0;
}
Se quisermos mudar o nosso valor de PI, ou da VERSAO, no programa acima, basta mexer no início do programa. Isto torna o programa mais flexível. Há quem diga que, em um programa, nunca se deve usar constantes como 10, 3.1416, etc., pois estes são números que ninguém sabe o que significam (muitas pessoas os chamam de "números mágicos"). Ao invés disto, deve-se usar apenas #defines. É uma convenção de programação (que deve ser seguida, pois torna o programa mais legível) na linguagem C que as macros declaradas em #defines devem ser todas em maiúsculas.
Um outro uso da diretiva #define é o de simplesmente definir uma macro. Neste caso usa-se a seguinte forma geral:
#define nome_da_macro
Neste caso o objetivo não é usar a macro no programa (pois ela seria substituída por nada), mas, sim, definir uma macro para ser usada como uma espécie de flag. Isto quer dizer que estamos definindo um valor como sendo "verdadeiro" para depois podermos testá-lo.
Também é possível definir macros com argumentos. Veja o exemplo a seguir:

#define max(A,B) ((A>B) ? (A):(B))
#define min(A,B) ((A
...
x = max(i,j);
y = min(t,r);
Embora pareça uma chamada de função, o uso de max (ou min) simplesmente substitui, em tempo de compilação, o código especificado. Cada ocorrência de um parâmetro formal (A ou B, na definição) será substituído pelo argumento real correspondente. Assim, a linha de código:
x = max(i,j);
será substituída pela linha:
x = ((i)>(j) ? (i):(j));
A linha de código:
x = max(p+q,r+s);
será substituída pela linha:
x = ((p+q)>(r+s) ? (p+q):(r+s));
Isto pode ser muito útil. Verifique que as macros max e min não possuem especificação de tipo. Logo, elas trabalham corretamente para qualquer tipo de dado, enquanto os argumentos passados forem coerentes. Mas isto pode trazer também algumas armadilhas. Veja que a linha
x = max(p++,r++);
será substituída pelo código
x = ((p++)>(r++) ? (p++):(r++));
e em consequência, incrementará o maior valor duas vezes.
Outra armadilha em macros está relacionada com o uso de parênteses. Seja a macro:
#define SQR(X) X*X
Imagine que você utilize esta macro na expressão abaixo:
y = SQR(A+B);
Ao fazer isto, a substituição que será efetuada não estará correta. A expressão gerada será:
y = A+B*A+B;
que obviamente é diferente de (A+B)*(A+B)   !

A solução para este problema é incluir parênteses na definição da macro:
#define SQR(X)(X)*(X)
Quando você utiliza a diretiva #define nunca deve haver espaços em branco no identificador. 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). Ao se tirar o espaço, a macro funcionará corretamente e poderá ser utilizada para imprimir o número inteiro i, saltando em seguida para a próxima linha.
            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.
AUTO AVALIAÇÃO
Veja como você está:  Escreva uma macro que retorne 1 se o seu argumento for um número ímpar e 0 se for um número par.

As Diretivas ifdef e endif

Nesta seção, e até mais a frente, veremos as diretivas de compilação condicional. Elas são muito parecidas com os comandos de execução condicional do C. As duas primeiras diretivas que veremos são as #ifdef e #endif. Suas formas gerais são:
#ifdef nome_da_macro
sequência_de_declarações
#endif
A sequência de declarações será compilada apenas se o nome da macro estiver definido. A diretiva de compilação #endif é util para definir o fim de uma sequência de declarações para todas as diretivas de compilação condicional. As linhas
#define PORT_0 0x378
...
/* Linhas de codigo qualquer... */
...
#ifdef PORT_0
  #define PORTA PORT_0
  #include "../sys/port.h"
#endif
demonstram como estas diretivas podem ser utilizadas. Caso PORT_0 tenha sido previamente definido, a macro PORTA é definida e o header file port.h é incluído.

A Diretiva ifndef

A diretiva #ifndef funciona ao contrário da diretiva #ifdef. Sua forma geral é:
#ifndef nome_da_macro
sequência_de_declarações
#endif
            A sequência de declarações será compilada se o nome da macro não tiver sido definido.

A Diretiva if

A diretiva #if tem a seguinte forma geral:
#if expressão_constante
sequência_de_declarações
#endif
A sequência de declarações será compilada se a expressão-constante for verdadeira. É muito importande ressaltar que a expressão fornecida deve ser constante, ou seja, não deve ter nenhuma variável.

A Diretiva else

A diretiva #else tem a seguinte forma geral:  
#if expressão_constante
sequência_de_declarações
#else
sequência_de_declarações
#endif
Ela funciona como seu correspondente, o comando else.
Imagine que você esteja trabalhando em um sistema, e deseje que todo o código possa ser compilado em duas diferentes plataformas (i.e. Unix e Dos). Para obter isto, você "encapsula" toda a parte de entrada e saída em arquivos separados, que serão carregados de acordo com o header file carregado. Isto pode ser facilmente implementado da seguinte forma:
#define SISTEMA DOS
...
/*linhas de codigo..*/
...
#if SISTEMA == DOS
  #define CABECALHO "dos_io.h"
#else
  #define CABECALHO "unix_io.h"
#endif
#include CABECALHO

A Diretiva elif

A diretiva #elif serve para implementar a estrutura if-else-if. Sua forma geral é:
#if expressão_constante_1
sequência_de_declarações_1
#elif expressão_constante_2
sequência_de_declarações_2
#elif expressão_constante_3
sequência_de_declarações_3
.
.
.
#elif expressão_constante_n
sequência_de_declarações_n
#endif
            O funcionamento desta estrutura é idêntico ao funcionamento apresentado anteriormente