Directiva #define
§1 Sinopsis
La directiva #define define una macro. Las macros proporcionan un mecanismo de reemplazo de tokens ( 3.2) con o sin una serie de parámetros formales (parecidos a las funciones). Esta similitud con las funciones hace que en ocasiones sirvan para una especie de sustitución inline ( 4.4.6b), aunque esta práctica presenta sus riesgos .
§2 Sintaxis
#define macro_identificador <secuencia-de-tokens>
§3 Comentario
Cada ocurrencia del macro_identificador en el código fuente es reemplazado en su misma posición por secuencia-de-tokens, que puede estar incluso vacío . Por ejemplo:
#define HOLA "Que tengas buen día!"
Cada vez que el macro identificador HOLA aparezca en el fuente será sustituido por la cadena señalada (hay algunas excepciones que se comentan a continuación). Esta sustitución es denominada macro-expansión, y la secuencia-de-tokens se denomina cuerpo de la macro.
Es tradición en C/C++ que los macro-identificadores sean expresados en mayúsculas:
#define adios "espero verte pronto" // no es muy "C"
#define ADIOS "espero verte pronto" // Mejor!!
§3.1 Recuerde que las directivas de preprocesador no terminan en punto y coma
;. Por ejemplo:
#define T int;
...
T* ptr = new T [10]; // Error !!
La sentencia anterior no es traducida a:
int* ptr = new int [10];
como podría suponerse. Sino a:
int;* ptr = new int; [10];
§3.2 La acción de la macro comienza
en el punto de su definición hasta el final del fichero , sin
embargo esta asociación entre el cuerpo de la macro (secuencia-de-tokens) y la etiqueta (macro_identificador) es
reversible. Es decir, se puede hacer desaparecer en cualquier punto (#undef
4.9.10j). Además, después que un
macro_identificador ha sido indefinido, puede ser redefinido con
#define, usando el mismo cuerpo de macro u otro distinto [2]. Ejemplo:
#include <iostream.h>
int main() { // =============
int x = 10;
cout << "Valor x == " << x << endl;
#define x 13
cout << "Valor x == " << x << endl;
#undef x
cout << "Valor x == " << x << endl;
}
Salida:
Valor x == 10
Valor x == 13
Valor x == 10
los nombres definidos en las macros no están sujetos a las reglas de visibilidad de los subespacios de nombres ( 4.1.11), lo que las hace altamente peligrosas, ya que el mecanismo de sustitución de tokens "arrasa" a lo largo de todo el código, efectuando cuantas sustituciones sean congruentes con su definición. A veces en sitios donde no pensábamos que lo haría y que, de otro modo, estarían relativamente a salvo de colisiones. Por ejemplo, en el interior de clases. Esta es una de las razones por las que se prefiere utilizar mayúsculas en los macro_identificadores; para disminuir la posibilidad de concordancias fortuitas. Por ejemplo, considere el siguiente código:
...
{
#define min -1
#define max 127
A a = foo(a, min, max); // L.m
...
B res = result (x, y, min); // L.n
}
... /* más adelante en un punto alejado */
int max = getmax(a, b); // L.k
En este caso, el programador ha previsto los #define min y max para mayor legibilidad y facilidad de modificación del código, como en las sentencias L.m y L.n, pero quizás más adelante, en L.k utiliza dicho nombre para definir una variable olvidando los #define anteriores y que estos tienen validez desde el punto de su definición hasta el final, con independencia de que hayan sido incluidos dentro del ámbito definido por los corchetes { }.
Como resultado de lo anterior, entre otras medidas, se considera buena práctica anular su efecto con #undef ( 4.9.10j) inmediatamente después de que dejen de ser necesarios. Por ejemplo, la forma correcta del código anterior podría ser:
...
{
#define min -1
#define max 127
A a = foo(a, min, max);
...
B res = result (x, y, min);
#undef min
#undef max
}
...
int max = getmax(a, b);
Sin embargo, un programador C++ de élite escribiría:
...
{
enum { min = -1, max = 127 };
A a = foo(a, min, max);
...
B res = result (x, y, min);
}
...
§3.3 Cualquier carácter encontrado en el cuerpo de la macro aparecerá en la macro-expansión. El cuerpo de la
macro termina en el primer carácter de nueva línea NL (10) que no esté precedido de (\). Cualquier secuencia de espacios y/o
comentarios en la línea de control, son reemplazados por un solo carácter de espacio.
§3.4 Un cuerpo de macro vacío está permitido. Ejemplo:
#define VACIO
Su efecto es doble, de forma que puede utilizarse con dos finalidades distintas, según se trate del código fuente, o de la acción de otros elementos del preprocesador.
- En lo que respecta a la lógica del preprocesador, si se encuentra una expresión como la anterior, a la que falta la secuencia-de-tokens, el macro_identificador adopta el valor 1, que es cierto (true). Este tipo de expresiones suele utilizarse en conjunción con la directivas de preproceso #ifdef e #ifndef ( 4.9.10e), que interrogan si un macro identificador está definido (es cierto) o no lo está (es falso).
- En lo que respecta al código fuente, la presencia de un cuerpo de macro vacío, supone la eliminación de cada macro-identificador del código fuente, ya que cada ocurrencia de VACIO es sustituido por un nulo.
§3.5 Después de cada macro-expansión se realiza una nueva exploración del código para ver si existen nuevas
expansiones, lo que permite la existencia de expansiones anidadas, ya que el texto después de una expansión puede contener
macro-identificadores que puedan sufrir otra expansión subsiguiente. Si la macro se expande a algo que parezca una directiva de
preprocesado, esta no será reconocida por el preprocesador.
Ejemplos
#define NIL ""
#define GETSTD #include <stdio.h>
#define forever for (;;); // Loop infinito
#define max(A, B) ((A) > (B) ? (A) : (B))
La última directiva origina que una línea en el fuente tal como:
x = max (p+q, r+s);
será transformada por el preprocesador en [3]:
x = ((p+q) > (r+s) ? (p+q) : (r+s));
§4 Como el lector puede suponer, las características de los define, combinadas con las directivas de
preprocesado condicionales #if y #else (
4.9.10d), representan un cúmulo de posibilidades para el programador.
// # define SP 1
// # define US 1
// # define FR 1
#if SP
# define ERRN "Error no recuperable en linea:"
#elif US
# define ERRN "Unrecoverable Error in line:"
#elif FR
# define ERRN "Error ne pas recuperable en line:"
#endif
Para cambiar el lenguaje de los mensajes en que aparezca ERRN en el fuente, solo hay que quitar el comentario a la línea correspondiente al idioma que queremos. Con este sencillo truco, un solo fuente puede servir para diversas versiones idiomáticas del programa [1].
§5 Limitaciones
La macro-expansión adolece de las siguientes limitaciones:
-
Cualquier ocurrencia del macro-identificador dentro de cadenas alfanuméricas, constantes carácter o comentarios en el código fuente son ignorados. Ejemplo:
#define pi 3.14159;
...
char* ptr = "Mostrar el valor de pi con decimales\n";
cout << ptr << endl;produciría la salida:
Mostrar el valor de pi con decimales
en vez de:
Mostrar el valor 3.14159 con decimales
-
Una macro no puede expandirse durante su propia expansión. Por ejemplo:
#define A A
no se expandirá indefinidamente.
En la sección 4.7 se señala como las variables enum pueden reemplazar con ventaja a los "defines", de forma que, como señala el propio Stroustrup, este tipo de variable C++ hace casi innecesario su uso.
§6 Inconvenientes
Las macros tienen su sitio y su justificación, pero también sus riesgos e inconvenientes. En general no se aconseja demasiado su uso y menos su abuso, porque dan lugar a un código difícil de leer. Además, cuando se utiliza como una especie de sustitución inline (4.4.6b) de funciones, presenta un inconveniente importante y es que con la macro no se realiza comprobación estática de tipos (uno de los mecanismos de prevención de errores de C++). Es decir, no existe comprobación de que el tipo de los parámetros formales coincide con los argumentos actuales ( 4.4.5), lo que puede generar errores difíciles de depurar. Además existe el riesgo de efectos colaterales indeseados, en especial cuando el argumento actual es evaluado más de una vez.
Como ejemplo de lo anterior, considere el siguiente programa que utiliza sucesivamente la función cubo y una macro CUBO, para calcular la potencia 3 de una cantidad.
int cubo(int x) { // función
return x*x*x;
}
#define CUBO(x) ( (x)* (x) * (x) ) // Macro sustitución (ver nota
)
...
int b = 0, a = 3;
b = cubo(a++); // L.7: cálculo mediante función
a = 3;
b = CUBO(a++); // L.9: sustitución y asignación posterior
En el primer caso, L.7: el argumento actual pasado a la función es 3, de forma que b = 3*3*3 == 27; a continuación, a es incrementado, con lo que: a == 4.
En el segundo caso, L.9: CUBO es sustituido por la macro correspondiente, por lo que la sentencia quedaría como:
b = ((a++)*(a++)*(a++)); // L.9-bis
el resultado es que b = 3*3*3 == 27, pero después, a sufre tres incrementos unitarios sucesivos, de forma que finalmente a == 6.
Observe que en este tipo de expresiones el paréntesis no puede estar separado de la macro:
#define CUBO(x) ( (x)* (x) * (x) ) // Ok.
#define CUBO (x) ( (x)* (x) * (x) ) // Error!!
§7 Las macros en la práctica
Como resultado de todo esto, aunque están ahí para usarlas, la mayoría de los textos y autores señalan que las macros, otrora bastante populares en C, son un recurso obsoleto que puede y debe ser evitado. No obstante, los ficheros de cabecera de los compiladores suelen estar plagados de macros, en especial para declarar constantes manifiestas ( 1.4.1a). Por ejemplo:
#define TABSIZE 100
...
int table[TABSIZE];
Sin embargo, la mayoría de las veces los ficheros de cabecera hacen uso de "defines" bastante sofisticadas. Por ejemplo, los que siguen pertenecen al compilador MS Visual C++:
#define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))
#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
#define LOBYTE(w) ((BYTE)(w))
#define HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF))
#define MAXUHALF_PTR 0xffff
#define MAXHALF_PTR 0x7fff
#define MINHALF_PTR 0x8000
#define HandleToUlong( h ) ((ULONG) (h) )
#define PtrToUlong( p ) ((ULONG) (p) )
#define PtrToLong( p ) ((LONG) (p) )
#define PtrToUshort( p ) ((unsigned short) (p) )
#define PtrToShort( p ) ((short) (p) )
Comentario
Algunas de las etiquetas utilizadas (por ejemplo, LONG, ULONG, DWORD o BYTE) son a su vez typedefs ( 3.2.1a) muy comunes en la programación de entornos Windows ( Ejemplos).
Las expresiones del tipo (WORD)(w) son expresiones de modelado de tipos ( 4.9.9).
Las expresiones del tipo 0xFFFF y 0xFF son constantes hexadecimales ( 3.2.3b).
§8 Convertir a cadenas con #
El símbolo # puede colocarse delante de un argumento formal de la macro para convertir el argumento actual a una cadena después de la sustitución (un proceso conocido como "stringification" por los angloparlantes). Por ejemplo, sea la directiva:
#define TRACE(flag) printf(#flag "=%d\n", flag)
y el código:
int highval = 1024;
TRACE(highval);
Después del preprocesado se transforma en:
int highval = 1024;
printf("highval" "=%d\n", highval);
que equivale a:
int highval = 1024;
printf("highval=%d\n", highval);
Vemos que en realidad se trata de una doble sustitución. En primer lugar se sustituye TRACE(...) por printf(#... "=%d\n", ...). Después se sustituye el literal expresado en el parámetro ...(flag) en todas las ocurrencias donde aparezca, si bien la sustitución ocurre de dos maneras posibles: como literal, entre comillas dobles, o sin comillas. En este caso, todas las ocurrencias de #flag son sustituidas por "highval", mientras que flag es sustutituido por highval (sin comillas).
Observe que este tipo de "macros" tienen una sintaxis parecida a las funciones y que, en estos casos, no es tan raro ver el identificador en minúsculas:
#define saludo(...) printf(...)
§8.1 Ejemplo
Utilizando la directiva
#define DPRINT(exp) printf(#exp " = %g\n", exp)
la línea de código:
DPRINT(x/y);
se transforma en:
printf("x/y" " = %g\n", x/y);
que equivale a:
printf("x/y =%g\n", x/y);
§8.2 Ejemplo operativo
#include <iostream.h>
#define mostrar(contenido) cout << "Resultado " #contenido
" == " << ": " << contenido << endl;
int main() { // ======================
int ai[] = {2, 3};
int z1 = ai[0] + ai[1];
mostrar(z1);
int* pt1 = ai; int z3 = *pt1;
mostrar(z3);
int* pt2 = ai; int z4 = *(++pt2);
mostrar(z4);
int* pt3 = ai; int z5 = *pt3 + *(++pt3);
mostrar(z5);
}
Salida:
Resultado z1 == : 5
Resultado z3 == : 2
Resultado z4 == : 3
Resultado z5 == : 6
§9 Doble almohadilla ##
los caracteres # y ## se usan también para realizar sustitución y asociación de tokens durante la fase de análisis del preprocesado. Este par de símbolos se conocen como token adhesivo ("Token paste"), porque permiten pegar o asociar dos tokens situados entre las dos almohadilla ( puede haber espacios opcionales en cada extremo).
En estos casos, el preprocesador elimina los espacios y la doble almohadilla, combinando los dos tokens en uno solo. Esto puede usarse para construir identificadores. Por ejemplo, dada la definición:
#define VAR(i, j) i##j
la llamada VAR(x, 6) se expande a x6.
Nota: este es el sistema nuevo, que reemplaza al antiguo (no estándar) método de utilizar:
#define VAR(i, j) (i/**/j)
Ejemplo
#define WCHAR( X ) L##X
#define WCHPT wchar_t*
la expresión:
WCHPT cptr = WCHAR("Hola mundo");
se transforma en:
wchar_t* cptr = L"Hola mundo";
En la página adjunta se muestra un ejemplo más elaborado de uso de esta directiva ( 4.9.10b1).
[1] Aparte de este recurso, C++ dispone de otras opciones para el caso concreto de versiones distintas de idioma, sistema de representación, moneda, etc. (localismos 5.2.2). En el epígrafe dedicado a la directiva ifdef ( 4.9.10e), se ha incluido un ejemplo con una versión más elegante para conseguir el mismo resultado.
[2] Esta posibilidad de redefinir e indefinir no está permitida con algunas constantes simbólicas utilizadas internamente por el compilador ( 1.4.1a)
[3] Como se verá en el epígrafe §6 , la utilización de "defines" no está exenta de inconvenientes, por lo que, en general, se desaconseja su utilización dentro de lo posible. Precisamente para evitar este tipo de problemas, el compilador GNU cpp dispone de dos operadores binarios específicos <? y >? que devuelven respectivamente el menor o mayor de dos valores.