AnteriorPosterior

11.2 Directivas del preprocesador

  Curso: Fundamentos de programación en C, por Nacho Cabanes

11.2 Directivas del preprocesador

Desde el principio hemos estado manejando cosas como

#include <stdio.h>

Y aquí hay que comentar bastante más de lo que parece. Ese “include” no es una orden del lenguaje C, sino una orden directa al compilador (una “directiva”). Realmente es una orden a una cierta parte del compilador que se llama “preprocesador”. Estas directivas indican una serie de pasos que se deben dar antes de empezar realmente a traducir nuestro programa fuente.

Aunque “include” es la directiva que ya conocemos, vamos a comenzar por otra más sencilla, y que nos resultará útil cuando lleguemos a ésta.

11.2.1. Constantes simbólicas: #define

La directiva “define” permite crear “constantes simbólicas”. Podemos crear una constante haciendo

#define MAXINTENTOS 10

y en nuestro programa lo usaríamos como si se tratara de cualquier variable o de cualquier valor numérico:

if (intentoActual >= MAXINTENTOS) ...

El primer paso que hace nuestro compilador es reemplazar esa “falsa constante” por su valor, de modo que la orden que realmente va a analizar es

if (intentoActual >= 10) ...

pero a cambio nosotros tenemos el valor numérico sólo al principio del programa, por lo que es muy fácil de modificar, mucho más que si tuviéramos que revisar el programa entero buscando dónde aparece ese 10.

Comparado con las constantes “de verdad”, que ya habíamos manejado (const int MAXINTENTOS=10;), las constantes simbólicas tienen la ventaja de que no son variables, por lo que no se les reserva memoria adicional y las comparaciones y demás operaciones suelen ser más rápidas que en el caso de un variable.

Vamos a ver un ejemplo completo, que pida varios números y muestre su suma y su media:

/*---------------------------*/
/*  Ejemplo en C nº 94:      */
/*  C094.C                   */
/*                           */
/*  Ejemplo de #define       */
/*                           */
/*  Curso de C,              */
/*    Nacho Cabanes          */
/*---------------------------*/
 
#include <stdio.h>
 
#define CANTIDADNUMEROS 5
 
int main() {
   int numero[CANTIDADNUMEROS];
   int suma=0;
   int i;
 
   for (i=0; i<CANTIDADNUMEROS; i++) {
     printf("Introduzca el dato número %d: ", i+1);
     scanf("%d", &numero[i]);
   }
 
   for (i=0; i<CANTIDADNUMEROS; i++)
     suma += numero[i];
   printf("Su suma es %d\n", suma);
   printf("Su media es %4.2f\n", (float) suma/CANTIDADNUMEROS);
 
   return 0;
}
 

Las constantes simbólicas se suelen escribir en mayúsculas por convenio, para que sean más fáciles de localizar. De hecho, hemos manejado hasta ahora muchas constantes simbólicas sin saberlo. La que más hemos empleado ha sido NULL, que es lo mismo que un 0, está declarada así:

#define NULL 0

Pero también en casos como la pantalla en modo texto con Turbo C aparecían otras constantes simbólicas, como éstas

#define BLUE 1
#define YELLOW 14

Y al acceder a ficheros teníamos otras constantes simbólicas como SEEK_SET (0), SEEK_CUR (1), SEEK_END (2).

 

A “define” también se le puede dar también un uso más avanzado: se puede crear “macros”, que en vez de limitarse a dar un valor a una variable pueden comportarse como pequeñas órdenes, más rápidas que una función. Un ejemplo podría ser:

#define SUMA(x,y) x+y

Vamos a aplicarlo en un fuente completo:

/*---------------------------*/
/*  Ejemplo en C nº 95:      */
/*  C095.C                   */
/*                           */
/*  Ejemplo de #define (2)   */
/*                           */
/*  Curso de C,              */
/*    Nacho Cabanes          */
/*---------------------------*/
 
#include <stdio.h>
 
#define SUMA(x,y) x+y
 
int main() {
   int n1, n2;
 
   printf("Introduzca el primer dato: ");
   scanf("%d", &n1);
 
   printf("Introduzca el segundo dato: ");
   scanf("%d", &n2);
 
   printf("Su suma es %d\n", SUMA(n1,n2));
   return 0;
}
 

11.2.2 Inclusión de ficheros: #include

Ya nos habíamos encontrado con esta directiva. Lo que hace es que cuando llega el momento de que nuestro compilador compruebe la sintaxis de nuestro fuente en C, ya no existe ese “include”, sino que en su lugar el compilador ya ha insertado los ficheros que le hemos indicado.

¿Y eso de por qué se escribe <stdio.h>, entre < y >? No es la única forma de usar #include. Podemos encontrar líneas como

#include <stdlib.h>

y como

#include "misdatos.h"

El primer caso es un fichero de cabecera estándar del compilador. Lo indicamos entre < y > y así el compilador sabe que tiene que buscarlo en su directorio (carpeta) de “includes”. El segundo caso es un fichero de cabecera que hemos creado nosotros, por lo que lo indicamos entre comillas, y así el compilador sabe que no debe buscarlo entre sus directorios, sino en el mismo directorio en el que está nuestro programa.

Vamos a ver un ejemplo: declararemos una función “suma” dentro de un fichero “.h” y lo incluiremos en nuestro fuente para poder utilizar esa función “suma” sin volver a definirla. Wl fichero de cabecera sería así:

/*---------------------------*/
/*  Ejemplo en C nº 96 (a):  */
/*  C096.H                   */
/*                           */
/*  Ejemplo de #include      */
/*                           */
/*  Curso de C,              */
/*    Nacho Cabanes          */
/*---------------------------*/
 
int suma(int x,int y) {
  return x+y;
}
 

(Nota: si somos puristas, esto no es correcto del todo. Un fichero de cabecera no debería contener los detalles de las funciones, sólo su “cabecera”, lo que habíamos llamado el “prototipo”, y la implementación de la función debería estar en otro fichero, pero eso lo haremos dentro de poco).

Un fuente que utilizara este fichero de cabecera podría ser

/*---------------------------*/
/*  Ejemplo en C nº 96 (b):  */
/*  C096.C                   */
/*                           */
/*  Ejemplo de #include (2)  */
/*                           */
/*  Curso de C,              */
/*    Nacho Cabanes          */
/*---------------------------*/
 
#include <stdio.h>
#include "c096.h"
 
int main() {
   int n1, n2;
 
   printf("Introduzca el primer dato: ");
   scanf("%d", &n1);
 
   printf("Introduzca el segundo dato: ");
   scanf("%d", &n2);
 
   printf("Su suma es %d\n", suma(n1,n2));
   return 0;
}
 

11.2.3. Compilación condicional: #ifdef, #endif

Hemos utilizado #define para crear constantes simbólicas. Desde dentro de nuestro fuente, podemos comprobar si está definida una constante, tanto si ha sido creada por nosotros como si la ha creado el sistema.

Cuando queramos que nuestro fuente funcione en varios sistemas distintos, podemos hacer que se ejecuten unas órdenes u otras según de qué compilador se trate, empleando #ifdef (o #if) al principio del bloque y #endif al final. Por ejemplo,

#ifdef _GCC_
instanteFinal = clock () + segundos * CLOCKS_PER_SEC ;
#endif
#ifdef _TURBOC_
instanteFinal = clock () + segundos * CLK_TCK ;
#endif

Los programas que utilicen esta idea podrían compilar sin ningún cambio en distintos sistemas operativos y/ distintos compiladores, y así nosotros esquivaríamos las incompatibilidades que pudieran existir entre ellos (a cambio, necesitamos saber qué constantes simbólicas define cada sistema).

Esta misma idea se puede aplicar a nuestros programas. Uno de los usos más frecuentes es hacer que ciertas partes del programa se pongan en funcionamiento durante la fase de depuración, pero no cuando el programa esté terminado.

Vamos a mejorar el ejemplo 94, para que nos muestre el valor temporal de la suma y nos ayude a descubrir errores:

(-- Fichero no encontrado --)

Este fuente tiene intencionadamente un error: no hemos dado un valor inicial a la suma, con lo que contendrá basura, y obtendremos un resultado incorrecto.

El resultado de nuestro programa sería

Introduzca el dato numero 1: 2
Introduzca el dato numero 2: 3
Introduzca el dato numero 3: 4
Introduzca el dato numero 4: 5
Introduzca el dato numero 5: 7
Valor actual de la suma: 2009055971
Valor actual de la suma: 2009055973
Valor actual de la suma: 2009055976
Valor actual de la suma: 2009055980
Valor actual de la suma: 2009055985
Su suma es 2009055992
Su media es 401811198.40

Vemos que ya en la primera pasada, el valor de la suma no es 2, sino algo que parece absurdo, así que falta el valor inicial de la suma, que debería ser “int suma=0;”. Cuando acaba la fase de depuración, basta con eliminar la frase #define DEPURANDO 1 (no es necesario borrarla, podemos dejarla comentada para que no haga efecto), de modo que el fuente corregido sería:

/*---------------------------*/
/*  Ejemplo en C nº 97b:     */
/*  C097b.C                  */
/*                           */
/*  Compilacion condicional  */
/*  con #define (2)          */
/*                           */
/*  Curso de C,              */
/*    Nacho Cabanes          */
/*---------------------------*/
 
#include <stdio.h>
 
#define CANTIDADNUMEROS 5
/*#define DEPURANDO 1*/
 
int main() {
   int numero[CANTIDADNUMEROS];
   int suma=0;   /* Error corregido */
   int i;
 
   for (i=0; i<CANTIDADNUMEROS; i++) {
     printf("Introduzca el dato número %d: ", i+1);
     scanf("%d", &numero[i]);
   }
 
   for (i=0; i<CANTIDADNUMEROS; i++) {
     #ifdef DEPURANDO
       printf("Valor actual de la suma: %d\n", suma);
     #endif       
     suma += numero[i];
   }
   printf("Su suma es %d\n", suma);
   printf("Su media es %4.2f\n", (float) suma/CANTIDADNUMEROS);
 
   return 0;
}
 

También tenemos otra alternativa: en vez de comentar la línea de #define, podemos anular la definición con la directiva #undef:

#undef DEPURANDO

Por otra parte, también tenemos una directiva #ifndef para indicar qué hacer si no está definida una contante simbólico, y un #else, al igual que en los “if” normales, para indicar qué hacer si no se cumple la condición, y una directiva #elif (abreviatura de “else if”), por si queremos encadenar varias condiciones.

11.2.4. Otras directivas

Las que hemos comentado son las directivas más habituales, pero también existen otras. Una de las que son frecuentes (pero menos estándar que las anteriores) es #pragma, que permite indicar opciones avanzadas específicas de cada compilador.

No veremos detalles de su uso en ningún compilador concreto, pero sí un ejemplo de las cosas que podemos encontrar su manejamos fuentes creados por otros programadores:

#if __SC__ || __RCC__
#pragma once
#endif

#ifndef RC_INVOKED
#pragma pack(__DEFALIGN)
#endif

Actualizado el: 16-01-2015 00:12

AnteriorPosterior