Subsecciones

12. El preprocesador de C

12.1 Directivas del preprocesador

Como se comento en el primer capítulo el preprocesamiento es el primer paso en la étapa de compilación de un programa -esta propiedad es única del compilador de C.

El preprocesador tiene más o menos su propio lenguaje el cual puede ser una herrramienta muy poderosa para el programador. Todas las directivas de preprocesador o comandos inician con un #.

Las ventajas que tiene usar el preprocesador son:

-
los programas son más fáciles de desarrollar,
-
son más fáciles de leer,
-
son más fáciles de modificar
-
y el código de C es más transportable entre diferentes arquitecturas de máquinas.

12.1.1 #define

El preprocesador también permite configurar el lenguaje. Por ejemplo, para cambiar a las sentencias de bloque de código { ... } delimitadores que haya inventado el programador como inicio ... fin se puede hacer:

#define inicio {
#define fin }

Durante la compilación todas las ocurrencias de inicio y fin serán reemplazadas por su correspondiente { o } delimitador y las siguientes étapas de compilación de C no encontrarán ninguna diferencia.

La directiva #define se usa para definir constantes o cualquier sustitución de macro. Su formato es el siguiente:

#define <nombre de macro> <nombre de reemplazo>

Por ejemplo:

#define FALSO 0
#define VERDADERO !FALSO

La directiva #define tiene otra poderosa característica: el nombre de macro puede tener argumentos. Cada vez que el compilador encuentra el nombre de macro, los argumentos reales encontrados en el programa reemplazan los argumentos asociados con el nombre de la macro. Por ejemplo:

#define MIN(a,b) (a < b) ? a : b

main()
{
	int x=10, y=20;

	printf("EL minimo es %d\n", MIN(x,y) );
}

Cuando se compila este programa, el compilador sustiuirá la expresión definida por MIN(x,y), excepto que x e y serán usados como los operandos. Así después de que el compilador hace la sustitución, la sentencia printf será ésta:

	printf("El minimo es %d\n", (x < y) ? x : y);

Como se puede observar donde se coloque MIN, el texto será reemplazado por la definición apropiada. Por lo tanto, si en el código se hubiera puesto algo como:

	x = MIN(q+r,s+t);

después del preprocesamiento, el código podría verse de la siguiente forma:

	x = ( q+r < s+t ) ? q+r : s+t;

Otros ejemplos usando #define pueden ser:

#define Deg_a_Rad(X) (X*M_PI/180.0)
/* Convierte grados sexagesimales a radianes, M_PI es el valor de pi */
/*    y esta definida en la biblioteca math.h */

#define IZQ_DESP_8 <<8

La última macro IZQ_DESP_8 es solamente válida en tanto el reemplazo del contexto es válido, por ejemplo: x = y IZQ_DESP_8.

El uso de la sustitución de macros en el lugar de las funciones reales tiene un beneficio importante: incrementa la velocidad del código porque no se penaliza con una llamada de función. Sin embargo, se paga este incremento de velocidad con un incremento en el tamaño del programa porque se duplica el código.

12.1.2 #undef

Se usa #undef para quitar una definición de nombre de macro que se haya definido previamente. El formato general es:

#undef <nombre de macro>

El uso principal de #undef es permitir localizar los nombres de macros sólo en las secciones de código que los necesiten.

12.1.3 #include

La directiva del preprocesador #include instruye al compilador para incluir otro archivo fuente que esta dado con esta directiva y de esta forma compilar otro archivo fuente. El archivo fuente que se leerá se debe encerrar entre comillas dobles o paréntesis de ángulo. Por ejemplo:

#include <archivo>

#include "archivo"

Cuando se indica <archivo> se le dice al compilador que busque donde estan los archivos incluidos o ``include'' del sistema. Usualmente los sistemas con UNIX guardan los archivos en el directorio /usr/include.

Si se usa la forma "archivo" es buscado en el directorio actual, es decir, donde el programa esta siendo ejecutado.

Los archivos incluidos usualmente contienen los prototipos de las funciones y las declaraciones de los archivos cabecera (header files) y no tienen código de C (algoritmos).

12.1.4 #if Inclusión condicional

La directiva #if evalua una expresión constante entera. Siempre se debe terminar con #endif para delimitir el fin de esta sentencia.

Se pueden así mismo evaluar otro código en caso se cumpla otra condición, o bien, cuando no se cumple ninguna usando #elif o #else respectivamente.

Por ejemplo,

#define MEX 0
#define EUA 1
#define FRAN 2

#define PAIS_ACTIVO MEX

#if PAIS_ACTIVO == MEX
	char moneda[]="pesos";
#elif PAIS_ACTIVO == EUA
	char moneda[]="dolar";
#else 
	char moneda[]="franco";
#endif

Otro método de compilación condicional usa las directivas #ifdef (si definido) y #ifndef (si no definido).

El formato general de #ifdef es:

#ifdef <nombre de macro>
<secuencia de sentecias>
#endif

Si el nombre de macro ha sido definido en una sentencia #define, se compilará la secuencia de sentecias entre el #ifdef y #endif.

El formato general de #ifdef es:

#ifndef <nombre de macro>
<secuencia de sentecias>
#endif

Las directivas anteriores son útiles para revisar si las macros están definidas -- tal vez por módulos diferentes o archivos de cabecera.

Por ejemplo, para poner el tamaño de un entero para un programa portable entre TurboC de DOS y un sistema operativo con UNIX, sabiendo que TurboC usa enteros de 16 bits y UNIX enteros de 32 bits, entonces si se quiere compilar para TurboC se puede definir una macro TURBOC, la cual será usada de la siguiente forma:

#ifdef TURBOC
#define INT_SIZE 16
#else
#define INT_SIZE 32
#endif

12.2 Control del preprocesador del compilador

Se puede usar el compilador gcc para controlar los valores dados o definidos en la línea de comandos. Esto permite alguna flexibilidad para configurar valores ademas de tener algunas otras funciones útiles. Para lo anterior, se usa la opción -Dmacro[=defn], por ejemplo:

gcc -DLONGLINEA=80 prog.c -o prog

que hubiese tenido el mismo resultado que

#define LONGLINEA 80

en caso de que hubiera dentro del programa (prog.c) algún #define o #undef pasaría por encima de la entrada de la línea de comandos, y el compilador podría mandar un ``warning'' sobre esta situación.

También se puede poner un símbolo sin valor, por ejemplo:

gcc -DDEBUG prog.c -o prog

En donde el valor que se toma es de 1 para esta macro.

Las aplicaciones pueden ser diversas, como por ejemplo cuando se quiere como una bandera para depuración se puede hacer algo como lo siguiente:

#ifdef DEBUG
	printf("Depurando: Versión del programa 1.0\n");
#else
	printf("Version del programa 1.0 (Estable)\n");
#endif

Como los comandos del preprocesador pueden estar en cualquier parte de un programa, se pueden filtrar variables para mostrar su valor, cuando se esta depurando, ver el siguiente ejemplo:

    x = y * 3;

    #ifdef DEBUG
        printf("Depurando: variables x e y iguales a \n",x,y);
    #endif

La opción -E hace que la compilación se detenga después de la étapa de preprocesamiento, por lo anterior no se esta propiamente compilando el programa. La salida del preprocesamiento es enviada a la entrada estándar. GCC ignora los archivos de entrada que no requieran preprocesamiento.

12.3 Otras directivas del preprocesador

La directiva #error forza al compilador a parar la compilación cuando la encuentra. Se usa principalmente para depuración. Por ejemplo:

#ifdef OS_MSDOS
    #include <msdos.h>
#elifdef OS_UNIX
    #include "default.h"
#else
    #error Sistema Operativo incorrecto
#endif

La directiva #line número ``cadena'' informa al preprocesador cual es el número siguiente de la línea de entrada. Cadena es opcional y nombra la siguiente línea de entrada. Esto es usado frecuentemente cuando son traducidos otros lenguajes a C. Por ejemplo, los mensajes de error producidos por el compilador de C pueden referenciar el nombre del archivo y la línea de la fuente original en vez de los archivos intermedios de C.

Por ejemplo, lo siguiente especifica que el contador de línea empezará con 100.

#line 100 "test.c"    /* inicializa el contador de linea y nombre de archivo */
main()                /* linea 100 */
{
    printf("%d\n",__LINE__); /* macro predefinida, linea 102 */
    printf("%s\n",__FILE__); /* macro predefinida para el nombre */
}

12.4 Ejercicios

  1. Definir una macro min(a,b) para determinar el entero más pequeño. Definir otra macro min3(a,b,c) en términos de min(a,b). Incorporar las macros en un programa demostrativo en donde se pida al usuario tres números y se muestre el más pequeño.
  2. Definir un nombre de macro de preprocesador para seleccionar: