¿Cuáles son las aplicaciones del operador de preprocesador ## y los errores que se deben tener en cuenta?

StackOverflow https://stackoverflow.com/questions/216875

Pregunta

Como mencioné en muchas de mis preguntas anteriores, estoy trabajando con K & amp; R y actualmente estoy en el preprocesador. Una de las cosas más interesantes, algo que nunca supe antes de cualquiera de mis intentos anteriores de aprender C, es el operador del preprocesador ## . Según K & amp; R:

  

El operador del preprocesador ##   Proporciona una forma de concatenar real.   Argumentos durante la expansión macro. Si un   parámetro en el texto de reemplazo es   adyacente a un ## , el parámetro es   reemplazado por el argumento real, el    ## y los espacios en blanco circundantes son   eliminado, y el resultado se vuelve a escanear.   Por ejemplo, la macro pegar   concatena sus dos argumentos:

     

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

     

así que pegar (nombre, 1) crea el token    name1 .

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

¿Fue útil?

Solución

CrashRpt: uso de ## para convertir cadenas de varios bytes de macro 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 char. Probablemente esto parezca que es realmente inútil, 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 junto a un __ DATE __ le daría un error de compilación.


Windows: utilizando ## para Unicode genérico o cadenas de varios 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, que se usan para los nombres de acceso y modificador limpios:

También he visto que se usa en el código para definir accesores 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 de nombres inteligentes.


Varias bibliotecas, usándolas 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;

Otros consejos

Una cosa que debe tener en cuenta cuando está utilizando los operadores de preprocesamiento de token-pegar (' ## ') o de ajuste de cadena (' # ') es que tienen que usar un nivel adicional de indirección para que funcionen correctamente en todos los casos.

Si no hace esto y los elementos pasados ??al operador de pegado de token son macros, obtendrá resultados que probablemente no sean los que desea:

#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 mensaje que encontré al actualizar a una nueva versión de un compilador:

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

Cuando el resultado del operador de pegado de token no es un token de preprocesador válido, el operador de pegado de token 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 pegado de 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 menos) 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 pegado de tokens al concatenar tokens de preprocesador a operadores de 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 El capítulo de documentación de GCC CPP sobre la concatenación tiene Más información útil sobre el operador de pegado de tokens.

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 una serie de 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 es no tener que escribir tanto fn_XpmFreeAttributes como " XpmFreeAttributes " (y corre el riesgo de escribir mal uno de ellos).

Una pregunta anterior en Stack & nbsp; Overflow solicitó un método sencillo para generar representaciones de cadena para las constantes de enumeración sin una gran cantidad de reescrituras propensas a errores.

Link

Mi respuesta a esa pregunta mostró cómo la aplicación de la pequeña magia de preprocesador le permite definir su enumeración de esta manera (por ejemplo) ...;

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

... Con la ventaja de que la expansión de macros 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, el color) en StringTable usando el operador ##. Las aplicaciones (¿trucos?) Como esta son donde los operadores # y ## son invaluables.

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

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 " derivados " objetos cuando lo haces:

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 incluso desea cambiar las definiciones y / o agregar métodos a los " objetos " ;.

SGlib usa ## para básicamente fusionar plantillas en C. Debido a que no hay una 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 con el nombre sglib_list_t_concat, y así sucesivamente.

Lo utilizo para una aserción basada en el hogar 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 utilizar el pegado de token cuando necesite concatenar parámetros de macro con otra cosa.

Se puede utilizar 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 para agregar prefijos personalizados a las variables definidas por macros. Así que algo 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 denominación. 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 denominación a la que se aplica: casi la mayoría de las convenciones de denominación utilizan una subcadena común para formar los nombres. Podría funcionar nombres (como arriba) o nombres de campos, nombres de variables o cualquier otra cosa.

Un uso importante en WinCE:

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

Al definir 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 el registro. Puedes hacer:

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

O, si su compilador no admite la función y la función :

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

Las funciones " arriba " " registra el mensaje y muestra exactamente qué función registró un mensaje.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top