santa - ¿Cuáles son las aplicaciones del## preprocessor operator y gotchas a considerar?
laser tag queretaro (13)
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 intentos 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, lapaste
macro concatena sus dos argumentos:
#define paste(front, back) front ## back
así que
paste(name, 1)
crea el tokenname1
.
¿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?
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 <stdio.h>
#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 compilador:
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 compilació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 compiladores, esto generará el resultado esperado:
1+2 std::vector
En otros compiladores, esto incluirá espacios en blanco no deseados:
1 + 2 std :: vector
Las versiones bastante modernas de GCC (> = 3.3 o más) no compilará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.
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.
Es muy útil para iniciar sesión. Tu puedes hacer:
#define LOG(msg) log_msg(__function__, ## msg)
O bien, si su compilador 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 sintaxis de C ++ podría no ser del todo correcta.
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).
Lo uso en programas 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".
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 ()
Lo uso para una afirmación de lanzamiento en casa en un compilador 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
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.
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)
Una pregunta anterior sobre solicitó un método sencillo para generar representaciones de cadenas para las constantes de enumeración sin una gran cantidad de errores de escritura.
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.
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.
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 compilació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;