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:
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.
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.
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).
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
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.
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 */ }
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.
unsigned char
.
unsigned char
.