Preprocesador de CEl preprocesador de C (cpp) es el preprocesador para el lenguaje de programación C. Es el primer programa invocado por el compilador y procesa directivas como #include, #define e #if. Estas directivas no son específicas de C. En realidad pueden ser usadas con cualquier tipo de archivo. El preprocesador utiliza 4 etapas denominadas Fases de traducción. Aunque alguna implementación puede elegir hacer alguna o todas las fases simultáneamente, debe comportarse como si fuesen ejecutadas paso a paso. Fases
EjemplosEsta sección trata con más detalle ejemplos de uso del preprocesador de C. Es crucial que existan buenas prácticas de programación cuando se escriben macros de C. Sobre todo cuando se trabaja en equipo. Incluyendo archivosLa forma más común de usar el preprocesador es incluir otro archivo: #include <stdio.h>
int main (void)
{
printf ("¡Hola Mundo!\n");
return 0;
}
El preprocesador reemplaza la línea También puede escribirse usando dobles comillas: Se puede utilizar cualquier extensión para los archivos de cabecera. Pero, por convención, se utiliza la extensión .h para los archivos de cabecera y .c para los archivos que no son incluidos por ningún otro. También pueden encontrarse otras extensiones. Por ejemplo, los archivos con extensión .def suelen ser archivos cuyo contenido está diseñado para ser incluido muchas veces.
#ifndef __ARCHIVO_H__
#define __ARCHIVO_H__
/*... declaraciones de funciones, etc. ...*/
#endif
Como resultado, al intentar inclurse el archivo por segunda vez, la operación "ifndef" va a dar falso porque __ARCHIVO_H__ ya estaba definido de la primera vez que se incluyó, y a consecuencia se saltea todo el bloque hasta llegar al "endif" que suele estar al final del archivo. Compilación condicionalLas directivas #define __WINDOWS__
#ifdef __WINDOWS__
#include <windows.h>
#else
#include <unistd.h>
#endif
La primera línea define una macro __WINDOWS__. Las macros pueden estar definidas por el compilador, se pueden especificar en la línea de comandos al compilador o pueden controlar la compilación del programa desde un archivo makefile. El código siguiente comprueba si la macro __WINDOWS__ está definida. Si es así, como en el ejemplo, se incluye el archivo Ejemplos de otros usosComo el preprocesador puede invocarse independientemente del proceso de compilación de código fuente también puede utilizarse como un preprocesador de propósito general para otros tipos de procesamiento de textos. Ver Preprocesadores de propósito general para ver otros ejemplos. Definición de macros y expansiónHay dos tipos de macros: las que son como objetos y las que son como funciones. Las que se asemejan a funciones toman parámetros mientras que las que se asemejan a objetos no. La forma de definir un identificador como una macro de cada tipo es, respectivamente: #define <identificador> <lista de tokens a reemplazar>
#define <identificador>(<lista de parámetros>) <lista de tokens a reemplazar>
Hay que tener en cuenta que no hay ningún espacio entre el identificador de la macro y el paréntesis izquierdo. Cada vez que el identificador aparezca en el código fuente será reemplazado por la lista de tokens. Incluso si está vacía. Los identificadores declarados como funciones solo se reemplazan cuando se invoca con los parámetros con los que se definió la macro. Las macros tipo objetos se usan normalmente como parte de prácticas de buena programación para crear nombres simbólicos para constantes. Por ejemplo: #define PI 3.14159
en vez de utilizar el número tal cual en el código. Un ejemplo de macro actuando como función es: #define RADAGRA(x) ((x) * 57.29578)
Esta macro define la forma de convertir radianes a grados. Después podemos escribir PrecedenciaHay que hacer notar que las macros usan paréntesis alrededor de los argumentos y alrededor de toda la expresión. Si se omite alguno de estos pueden darse efectos no deseados más tarde. Por ejemplo:
probablemente ninguno de los dos se corresponda con el efecto deseado. Evaluación múltiple de efectos colateralesOtro ejemplo de macros tipo función es: #define MIN(a,b) ((a)>(b)?(b):(a))
Este ilustra uno de los peligros de usar macros como funciones. Uno de los argumentos (a o b) será evaluado dos veces cuando se llame a la "función". Así que si se evalúa la expresión Una forma más segura de lograr el mismo objetivo es usar la construcción typeof. #define max(a,b) \
({ typeof (a) _a = (a); \
typeof (b) _b = (b); \
_a > _b ? _a : _b; })
Esto logra que los argumentos solo se evalúen una vez. Y, además, que no sea específico del tipo de datos. Esta construcción no es legal en ANSI C. Tanto la palabra clave Con ANSI C no hay una solución general para afrontar el efecto colateral de los argumentos en macros. Concatenación de cadenasLa concatenación de cadenas es una de las características más sutiles, y de la que se puede abusar más fácilmente. Dos argumentos pueden unirse usando el operador Por ejemplo: #define MYCASE(_elemento,_id) \
case _id: \
_elemento##_##_id=_id;\
break
switch(x) {
MYCASE(widget,23);
}
La línea Hay que tener en cuenta que el _ entre Punto y comaComo se ve en el ejemplo anterior el punto y coma de la última línea de la definición de la macro se omite y la macro parece 'natural' cuando se escribe. Se puede incluir en la definición de la macro pero entonces nos encontraremos con líneas en el código sin punto y coma al final. Esto puede despistar a cualquiera que lea el código. O, aún peor, el usuario puede estar tentado a incluir puntos y coma de todas formas. En la mayoría de las ocasiones puede ser inofensivo (un punto y coma adicional denota una sentencia vacía) pero puede causar errores en los bloques de control de flujo: #define PRINT_FORMATEADO(s) \
printf ("Mensaje: \"%s\"\n", s);
if (n < 10)
PRINT_FORMATEADO("n es menor que 10");
else
PRINT_FORMATEADO("n es como mínimo 10");
Esto se expande en dos sentencias. La que se pretende
Líneas múltiplesLas macros pueden extenderse tantas líneas como hagan falta. Para ello tan solo habrá que terminar cada línea con una raya a la izquierda (\). La macro terminará en la última línea sin una raya a la izquierda. Adecuadamente usadas las macros con varias líneas pueden reducir mucho el tamaño y la complejidad del código fuente de un programa en C. Así se aumenta la legibilidad y mantenibilidad del código. Entrecomillando los argumentos de las macrosAunque una cadena pasada a una macro no esté encerrada entre comillas se puede tratar como si lo estuviese usando la directiva "#". Por ejemplo en la macro: #define ENTRECOMILLAR(x) #x
el código printf("%s\n", ENTRECOMILLAR(1+2));
se expandirá como printf("%s\n", "1+2");
Esta capacidad puede usarse con la concatenación de cadenas automáticas para depurar macros. Como en el ejemplo siguiente: #define dumpme(x, fmt) printf("%s:%u: %s=" fmt, __FILE__, __LINE__, #x, x)
int alguna_funcion() {
int foo;
/* [aquí va un montón de código complicado] */
dumpme(foo, "%d");
/* [y aquí más código complicado] */
}
imprimirá el nombre de la expresión y su valor así como el nombre del archivo y la línea donde se ejecuta. Macros variablesEl ANSI C no permite macros que tengan un número variable de argumentos. Pero esta opción fue introducida por varios compiladores y se estandarizó en C99. Las macros variables son particularmente útiles cuando escribimos envolturas para Macros XUn patrón de uso poco conocido del preprocesador de C se conoce como "Macros X". Son la práctica de usar varias veces la directiva Archivo: comandos.def
COMANDO(ADD, "Comando para añadir")
COMANDO(SUB, "Comando para restar")
COMANDO(XOR, "Comando O-Exclusivo")
enum command_indices {
#define COMANDO(nombre, descripcion) COMANDO_##nombre ,
#include "commandos.def"
#undef COMANDO
NUMERO_COMANDOS /* El número de comandos definidos */
};
char *descripciones_comandos[] = {
#define COMMANDO(nombre, descripcion) descripcion ,
#include "comandos.def"
#undef COMANDO
NULL
};
resultado_t gestor_ADD (estado_t *)
{
/* código para ADD aquí */
}
resultado_t gestor_SUB (estado_t *)
{
/* código para SUB aquí */
}
resultado_t gestor_XOR (estado_t *)
{
/* código para XOR aquí */
}
typedef resultado_t (*gestor_comando_t)(estado_t *);
gestor_comando_t gestores_comandos[] = {
#define COMANDO(nombre, descripcion) &gestor_##nombre,
#include "comandos.def"
#undef COMANDO
NULL
};
Cuando se quiera añadir un nuevo comando tan solo habrá que modificar el archivo de cabecera donde hemos definido la Macro X (normalmente con la extensión .def) y definir un nuevo gestor para ese comando. La lista de descripciones, la lista de gestores y la enumeración se actualizarán automáticamente por el preprocesador. Sin embargo en muchos casos esta automatización no es posible. Errores y advertencias de compilación definidas por el usuarioLa directiva #error "¡ERROR GRAVISIMO!"
Esto muestra "¡ERROR GRAVISIMO!" en la salida del compilador y para la compilación en este punto. Esto es muy útil si no sabes si una línea es compilada o no. También es útil cuando tienes el código muy parametrizado y quieres saber si un #ifdef WINDOWS
... /* código específico de Windows */
#elif defined(UNIX)
... /* código específico de Unix */
#else
#error "¿ Cuál es tu Sistema Operativo ?"
#endif
También se pueda usar la directiva #warning "ABC está obsoleto. Use XYZ en su lugar."
Visual Studio.NET no soporta esta directiva y debe usarse la directiva Aunque el texto de las directivas #error y #warning no tiene que estar entrecomillado es una buena práctica usarlo. Si no se usan, se pueden encontrar errores con los apostrofes y otros caracteres que puede intentar interpretar el preprocesador. Características del procesador específicas del compiladorLas directivas Estándar de colocación de macrosAlgunos símbolos están predefinidos en ANSI C. Los más usuales son // depurando macros para poder ver de dónde proviene un mensaje.
#define CADENADONDE "[archivo %s, línea %d] "
#define ARGUMENTOSDONDE __FILE__,__LINE__
printf (CADENADONDE ": mira, x=%d\n", ARGUMENTOSDONDE, x);
Esto muestra el valor de Macros específicas del compilador predefinidasNormalmente hay que revisar la documentación específica del compilador. O ver el proyecto[1] que mantiene una lista con las particularidades de cada compilador. También se puede hacer que el compilador nos diga algunas macros predefinidas útiles:
Véase tambiénReferencias
Enlaces externos
|