¿Cuáles son las aplicaciones del ## preprocessor operator y gotchas a considerar?

Como mencioné en muchas de mis preguntas anteriores, estoy trabajando con K & R, y actualmente estoy en el preprocesador. Una de las cosas más interesantes, algo que nunca antes supe de ninguno de mis bashs previos de aprender C, es el ## operador de preprocesador. De acuerdo con K & R:

El operador del preprocesador ## proporciona una forma de concatenar los argumentos reales durante la expansión de la macro. Si un parámetro en el texto de reemplazo es adyacente a un ## , el parámetro se reemplaza por el argumento real, el ## y el espacio en blanco circundante se eliminan, y el resultado se vuelve a analizar. Por ejemplo, la paste macro concatena sus dos argumentos:

#define paste(front, back) front ## back

así que paste(name, 1) crea el token name1 .

¿Cómo y por qué alguien usaría esto en el mundo real? ¿Cuáles son los ejemplos prácticos de su uso y hay trampas que considerar?

CrashRpt: Usar ## para convertir cadenas macro de múltiples bytes a Unicode

Un uso interesante en CrashRpt (biblioteca de informes de fallos) es el siguiente:

 #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) //Note you need a WIDEN2 so that __DATE__ will evaluate first. 

Aquí quieren usar una cadena de dos bytes en lugar de una cadena de un byte por cadena. Esto probablemente parece que no tiene sentido, pero lo hacen por una buena razón.

  std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__); 

Lo usan con otra macro que devuelve una cadena con la fecha y la hora.

Poner L lado de un __ DATE __ le daría un error de comstackción.


Windows: se usa ## para cadenas genéricas Unicode o de múltiples bytes

Windows usa algo como lo siguiente:

 #ifdef _UNICODE #define _T(x) L ## x #else #define _T(x) x #endif 

Y _T se usa en todas partes en el código


Varias bibliotecas, utilizando para acceso limpio y nombres de modificador:

También lo he visto utilizado en el código para definir accesos y modificadores:

 #define MYLIB_ACCESSOR(name) (Get##name) #define MYLIB_MODIFIER(name) (Set##name) 

Del mismo modo, puede utilizar este mismo método para cualquier otro tipo de creación inteligente de nombres.


Varias bibliotecas, usándolo para hacer varias declaraciones de variables a la vez:

 #define CREATE_3_VARS(name) name##1, name##2, name##3 int CREATE_3_VARS(myInts); myInts1 = 13; myInts2 = 19; myInts3 = 77; 

Algo que debe tener en cuenta cuando utiliza los operadores de preprocesamiento token-paste (‘ ## ‘) o stringizing (‘ # ‘) es que debe usar un nivel adicional de direccionamiento indirecto para que funcionen correctamente en todos los casos.

Si no hace esto y los elementos pasados ​​al operador de pegado de fichas son macros, obtendrá resultados que probablemente no sean los que usted quiere:

 #include  #define STRINGIFY2( x) #x #define STRINGIFY(x) STRINGIFY2(x) #define PASTE2( a, b) a##b #define PASTE( a, b) PASTE2( a, b) #define BAD_PASTE(x,y) x##y #define BAD_STRINGIFY(x) #x #define SOME_MACRO function_name int main() { printf( "buggy results:\n"); printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__))); printf( "\n" "desired result:\n"); printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__))); } 

La salida:

 buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21 

Aquí hay un error que me encontré al actualizar a una nueva versión de un comstackdor:

El uso innecesario del operador de pegar fichas ( ## ) no es portátil y puede generar espacios en blanco, advertencias o errores no deseados.

Cuando el resultado del operador token-pasting no es un token preprocesador válido, el operador token-pasting es innecesario y posiblemente dañino.

Por ejemplo, uno podría intentar construir literales de cadena en tiempo de comstackción usando el operador de pegar token:

 #define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a##+##b) #define NS(a, b) STRINGIFY(a##::##b) printf("%s %s\n", PLUS(1,2), NS(std,vector)); 

En algunos comstackdores, esto generará el resultado esperado:

 1+2 std::vector 

En otros comstackdores, esto incluirá espacios en blanco no deseados:

 1 + 2 std :: vector 

Las versiones bastante modernas de GCC (> = 3.3 o más) no comstackrán este código:

 foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token 

La solución es omitir el operador de pegar token al concatenar tokens de preprocesador a operadores C / C ++:

 #define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a+b) #define NS(a, b) STRINGIFY(a::b) printf("%s %s\n", PLUS(1,2), NS(std,vector)); 

El capítulo de documentación de GCC CPP sobre concatenación tiene más información útil sobre el operador de pegar token.

Esto es útil en todo tipo de situaciones para no repetirse innecesariamente. El siguiente es un ejemplo del código fuente de Emacs. Nos gustaría cargar varias funciones de una biblioteca. La función “foo” debe asignarse a fn_foo , y así sucesivamente. Definimos la siguiente macro:

 #define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ } 

Entonces podemos usarlo:

 LOAD_IMGLIB_FN (library, XpmFreeAttributes); LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer); LOAD_IMGLIB_FN (library, XpmReadFileToImage); LOAD_IMGLIB_FN (library, XImageFree); 

El beneficio no es tener que escribir tanto fn_XpmFreeAttributes y "XpmFreeAttributes" (y arriesgarse a escribir mal uno de ellos).

Una pregunta anterior sobre Stack Overflow solicitó un método sencillo para generar representaciones de cadenas para las constantes de enumeración sin una gran cantidad de errores de escritura.

Enlazar

Mi respuesta a esa pregunta mostró cómo aplicar magia preprocesadora pequeña le permite definir su enumeración de esta manera (por ejemplo) …;

 ENUM_BEGIN( Color ) ENUM(RED), ENUM(GREEN), ENUM(BLUE) ENUM_END( Color ) 

… Con el beneficio de que la macro expansión no solo define la enumeración (en un archivo .h), también define una matriz de cadenas coincidentes (en un archivo .c);

 const char *ColorStringTable[] = { "RED", "GREEN", "BLUE" }; 

El nombre de la tabla de cadenas proviene de pegar el parámetro de macro (es decir, Color) a StringTable usando el operador ##. Las aplicaciones (¿trucos?) Como esta son donde los operadores # y ## son invaluables.

Puede usar el pegado de tokens cuando necesite concatenar parámetros macro con algo más.

Se puede usar para plantillas:

 #define LINKED_LIST(A) struct list##_##A {\ A value; \ struct list##_##A *next; \ }; 

En este caso, LINKED_LIST (int) te daría

 struct list_int { int value; struct list_int *next; }; 

Del mismo modo, puede escribir una plantilla de función para el recorrido de la lista.

Lo uso en progtwigs C para ayudar a aplicar correctamente los prototipos para un conjunto de métodos que deben ajustarse a algún tipo de convención de llamadas. En cierto modo, esto se puede utilizar para la orientación a objetos del hombre pobre en C recta:

 SCREEN_HANDLER( activeCall ) 

se expande a algo como esto:

 STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst ); 

Esto impone la parametrización correcta para todos los objetos “derivados” cuando lo hace:

 SCREEN_HANDLER( activeCall ) SCREEN_HANDLER( ringingCall ) SCREEN_HANDLER( heldCall ) 

lo anterior en sus archivos de encabezado, etc. También es útil para el mantenimiento si desea cambiar las definiciones y / o agregar métodos a los “objetos”.

SGlib usa ## para básicamente fundir plantillas en C. Debido a que no hay sobrecarga de funciones, ## se usa para pegar el nombre del tipo en los nombres de las funciones generadas. Si tuviera un tipo de lista llamado list_t, obtendría funciones nombradas como sglib_list_t_concat, y así sucesivamente.

Lo uso para una afirmación de lanzamiento en casa en un comstackdor de C no estándar para incrustado:

#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us 

Lo uso para agregar prefijos personalizados a las variables definidas por las macros. Entonces algo así como:

 UNITTEST(test_name) 

se expande a:

 void __testframework_test_name () 

El uso principal es cuando tiene una convención de nomenclatura y desea que su macro aproveche esa convención de nomenclatura. Quizás tenga varias familias de métodos: image_create (), image_activate (), y image_release () también file_create (), file_activate (), file_release (), y mobile_create (), mobile_activate () y mobile_release ().

Podría escribir una macro para manejar el ciclo de vida del objeto:

 #define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release()) 

Por supuesto, una especie de “versión mínima de objetos” no es el único tipo de convención de nomenclatura a la que esto se aplica: casi la gran mayoría de las convenciones de nomenclatura utilizan una subcadena común para formar los nombres. Me podría nombres de funciones (como arriba), o nombres de campos, nombres de variables, o casi cualquier otra cosa.

Un uso importante en WinCE:

 #define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT)) 

Mientras definimos la descripción del bit de registro hacemos lo siguiente:

 #define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7 

Y mientras usa BITFMASK, simplemente use:

 BITFMASK(ADDR) 

Es muy útil para iniciar sesión. Tu puedes hacer:

 #define LOG(msg) log_msg(__function__, ## msg) 

O bien, si su comstackdor no es compatible con func y func :

 #define LOG(msg) log_msg(__file__, __line__, ## msg) 

El “funciones” anterior registra el mensaje y muestra exactamente qué función registró un mensaje.

Mi syntax de C ++ podría no ser del todo correcta.