El preprocesador es un programa que forma parte del compilador y que “prepara” o modifica el código fuente antes de ser traducido a código binario. Los cambios los hace interpretando aquellas líneas del código que comienzan por el símbolo “#”. El uso de estas directivas son tan comunes en los programas en C que parece que forman parte del lenguaje, pero en realidad son parte de un lenguaje que sólo entiende el procesador. La siguiente figura ilustra como se procesa un fichero de código fuente en realidad por el compilador.
Como se puede comprobar, las dos líneas que comienzan por “#” han desaparecido, al igual que los comentarios. Al traductor a código binario le llega fichero sin ninguna directiva ni comentario.
El preprocesador puede ser utilizado de forma independiente
del compilador mediante el comando cpp
. Abre un terminal de
comandos y consulta la página de manual de este comando. Como puedes
comprobar, es un programa que puede procesar un amplio catálogo de
directivas. De todas ellas, en este documento se describen las más
utilizadas.
#include
La directiva #include
debe ir seguida del
nombre de un fichero y su efecto es de reemplazar esta línea en el código
fuente por el contenido del fichero que se especifica. La siguiente figura
ilustra un ejemplo de esta directiva.
Con esta directiva debes tener en cuenta lo siguiente:
Si el fichero que va a continuación de
#include
está rodeado por “<” y
“>”, el preprocesador lo busca en los directorios
internos del sistema. Esta versión se utiliza por tanto para incluir
ficheros dados por el sistema.
Si el fichero está rodeado de doble comilla, entonces se busca en el directorio en el que se está compilando. Esta versión de la directiva se utiliza para incluir ficheros que ha escrito el usuario. La opción -L del compilador permite especificar una lista adicional de directorios en los que buscar este tipo de ficheros.
La extensión “.h
” se
suele utilizar para ficheros que se incluyen en un programa con esta
directiva
Antes de incluir el contenido del fichero, se procesan las directivas contenidas en su interior. Esto permite que un fichero que se incluye en un programa incluya a su vez otros.
Este tipo de ficheros suele incluir definiciones que se
necesitan en más de un fichero. En lugar de repetir estas definiciones
en los ficheros de código (con extensión
“.c
”), se pasan a un fichero con
extensión “.h
” que se incluye en todos
ellos.
Los ficheros incluidos con la directiva
#include
no se escriben en el comando que invoca
al compilador. Su utilización la decide el preprocesador cuando
encuentra la directiva #include
.
#define
La directiva #define
tiene dos versiones. Si va
seguida de una única cadena de texto como por ejemplo
#define SISTEMA_MAEMO
el preprocesador simplemente anota internamente que este símbolo está “definido”. En la sección 11.3 veremos otra directiva para consultar qué símbolos están definidos.
La segunda versión de esta directiva es cuando va seguida de
dos cadenas de texto. En este caso, a partir de ese punto, el preprocesador
reemplaza toda aparición de la primera cadena por la segunda. El siguiente
ejemplo define el símbolo CARGA_MAXIMA
para que se reemplace
por el valor 1500
.
#define CARGA_MAXIMA 1500
El efecto de esta directiva es idéntico
al uso de la opción -D del compilador. En realidad,
cuando se invoca el compilador con la opción
-Dnombre=valor, esta definición es equivalente a que el
procesador encontrase la línea #define nombre valor
.
Con la directiva #define
debes tener en cuenta
lo siguiente:
Esta directiva suele estar al comienzo de los ficheros
de código, o si se necesitan en varios ficheros, en un fichero de
definiciones con extensión “.h
” que se
incluye en otros ficheros.
Para diferenciar en el código los símbolos normales de
un programa de aquellos que han sido definidos por la directiva
#define
y que van a ser reemplazados por sus equivalentes
por el preprocesador, estos últimos se suelen escribir siempre con
mayúsculas (esto es una convención, el preprocesador no realiza ningún
tipo de comprobación).
El reemplazo del símbolo por su valor se realiza en todo el texto de un fichero. Esto incluye también las propias directivas del preprocesador. En el siguiente ejemplo
#define UNO 1
#define OTRO_UNO UNO
int main(argc, char *argv[])
{
printf("%d\n", OTRO_UNO);
}
el programa imprime el número uno. Es decir, la segunda
directiva #define
se procesa como #define OTRO_UNO
1
al reemplazarse UNO
por la definición de la línea
anterior.
#ifdef
, #else
y
#endif
En la sección 11.2 hemos visto como
el preprocesador mantiene un conjunto símbolos definidos, y algunos de ellos
deben ser sustituidos por sus valores equivalentes. El preprocesador también
ofrece un mecanismo por el que una porción de código de un programa se puede
ocultar o considerar dependiendo del valor de alguno de los símbolos
definidos con la directiva #define
. La estructura de esta
construcción es la siguiente:
#ifdef SIMBOLO
/* Bloque de código 1 */
...
#else
/* Bloque de código 2 */
#endif
Cuando el preprocesador encuentra la primera directiva
#ifdef SIMBOLO
, si SIMBOLO
está definido, pasa el
bloque de código 1 al compilador (hasta la directiva #else
) y
elimina el bloque de código 2 (entre las directivas #else
y
#endif
. De forma análoga, si SIMBOLO
no está
definido, el bloque de código 1 se elimina y el compilador sólo recibe el
bloque de código 2. Esta construcción es similar al
if/then/else
en C, pero la diferencia es que esta la interpreta
el preprocesador cuando se compila. La diferencia está en que si el bloque
de código que se ignora contiene un error de sintaxis, el compilador
generará el programa igualmente, pues no llega a procesar ese código.
Esta directiva se utiliza cuando se quiere mantener dos
versiones de un programa que se diferencian únicamente en un reducido número
de líneas de código. Las dos versiones pueden coexistir en el código fuente
pero rodeadas de esta directiva. Al compilar se utiliza entonces la opción
-Dnombre=valor
para seleccionar los bloques de código
pertinentes y generar el ejecutable.
El siguiente ejemplo muestra el uso de esta directiva para
escribir dos versiones de un mensaje de bienvenida a un sistema con dos
posibles versiones. Si el símbolo MAEMO
está definido (por
ejemplo al compilar gcc -DMAEMO ...) al ejecutar se
imprime un mensaje, y si no está definido este símbolo, se imprime un
mensaje alternativo.
#ifdef MAEMO
printf("Bienvenido al sistema Maemo\n"); /* Código para la versión MAEMO */
#else
printf("Bienvenido al otro sistema\n"); /* Código para la otra versión */
#endif
#define
La directiva #define
tiene una funcionalidad
extra que puede utilizarse para definir lo que se conoce como
“macros”. El reemplazo que hace el preprocesador del símbolo
por su equivalente puede incluir parámetros. En el siguiente ejemplo se
define una macro para reemplazar el símbolo DEMASIADO_GRANDE(v)
la comparación de v
dada con el valor 1000.
#define DEMASIADO_GRANDE(v) (v >= 1000)
La macro DEMASIADO_GRANDE(v)
se puede utilizar
en el código con un nombre de variable en lugar de v
que será
utilizado al reemplazarse el símbolo por su equivalente tal y como se
muestra en el siguiente ejemplo:
int i;
if (DEMASIADO_GRANDE(i)) /* Código fuente */
{
...
}
if ((i >= 1000)) /* Código recibido por el traductor */
{
...
}