2008-10-19 10 views
82

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 anteriores de aprender C — es el operador de preprocesador ##. Según K & R:¿Cuáles son las aplicaciones del ## preprocessor operator y gotchas a considerar?

El preprocesador operador ## proporciona una manera para concatenar reales 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 que rodea el espacio en blanco se eliminan , y el resultado se vuelve a escanear. Por ejemplo, la macro paste concatena sus dos argumentos:

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

por lo 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?

Respuesta

44

CrashRpt: El uso de ## para convertir cadenas multibyte macro a Unicode

Un uso interesante de CrashRpt (biblioteca de informes de bloqueo) es la 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 junto a __ DATE __ daría un error de compilación.


de Windows: El uso de ## para las cadenas Unicode genérico o de múltiples bytes

de Windows utiliza algo como lo siguiente:

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

Y _T se utiliza en todas partes de código


Varias bibliotecas, utilizando los nombres de descriptor de acceso y modificadores limpias:

también he visto que se usa en el código para definir descriptores de acceso y modificadores:

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

Del mismo modo se puede utilizar este mismo método para cualquier otro tipo de ingeniosa creación de nombre.


Varias bibliotecas, que lo utilizan 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; 
+3

Ya que se puede concatenar cadenas literales en tiempo de compilación, que podría reducir la expresión builddate a 'std :: wstring builddate = WIDEN (__DATE__) L "" WIDEN (__ TIME __); 'y construye implícitamente toda la cadena a la vez. – user666412

1

lo uso para la adición de prefijos personalizados a las variables definidas por las macros. Así que algo como:

UNITTEST(test_name) 

expande a:

void __testframework_test_name() 
3

Puede utilizar pegar el símbolo cuando usted necesita para concatenar los parámetros macro con otra cosa.

Puede ser utilizado para las plantillas:

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

En este caso LINKED_LIST (int) le daría

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

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

6

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); 

La ventaja es no tener que escribir tanto fn_XpmFreeAttributes y "XpmFreeAttributes" (y el riesgo de falta de ortografía uno de ellos).

1

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().

Se puede escribir una macro para la manipulación de objetos ciclo de vida:

#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 la única especie de convención de nomenclatura esto se aplica a - casi la gran mayoría de las convenciones de nomenclatura hacen uso de 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.

2

Lo uso en programas 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 llamadas. En cierto modo, esto puede ser utilizado para la orientación a objetos del hombre pobre en C recta:

SCREEN_HANDLER(activeCall) 

amplía a algo como esto:

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

Esto refuerza la parametrización correcta para todos "derivar" objetos cuando se hacen :

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

lo anterior en sus archivos de cabecera, etc. también es útil para el mantenimiento, incluso si sucede que desee cambiar las definiciones y/o añadir métodos a los "objetos".

2

SGlib usa ## para básicamente plantillas de fundido de azúcar 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.

0

Es muy útil para el registro. Que puede hacer:

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

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

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

Las "funciones" por encima de los registros de mensajes y muestra exactamente qué función registra un mensaje .

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

+1

¿Qué intentabas hacer con eso? Funcionaría igual de bien sin el "##", ya que no hay necesidad de pegar y pegar "," a "msg". ¿Estabas tratando de stringify msg? Además, __FILE__ y __LINE__ deben estar en mayúsculas, no en minúsculas. – bk1e

+0

Tienes razón en verdad. Necesito encontrar el script original para ver cómo se usó ##. Lástima de mí, no hay galletas hoy! – ya23

14

Aquí hay una Gotcha que me encontré cuando se actualiza a una nueva versión de un compilador:

uso innecesario del operador token-pegar (##) no es portátil y puede generar no deseada de espacio en blanco, advertencias, o errores

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, se podría tratar de construir literales de cadena en tiempo de compilación usando el operador de red en pegar:

#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, esta es la salida el resultado esperado:

1+2 std::vector 

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

1 + 2 std :: vector 

Fairly modern versi ons de GCC (> = 3,3 o así) fallará para compilar 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 token-pegar al concatenar tokens preprocesador a los 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 GCC CPP documentation chapter on concatenation tiene más información útil sobre el operador de token-pegar.

+0

Gracias - No era consciente de esto (pero entonces no uso demasiado estos operadores de preprocesamiento ...). –

+3

Se llama el operador "pegar token" por una razón: la intención es terminar con un solo token cuando haya terminado. Buen comentario. –

+0

Cuando el resultado del operador token-pasting no es un token de preprocesador válido, el comportamiento no está definido. – alecov

2

lo uso para un hogar rodaron afirman 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 

 
+3

Supongo que quiere decir con 'no estándar' que el compilador no pegó cadenas, pero sí pegó fichas, ¿o hubiera funcionado incluso sin '##'? – PJTraill

4

Una pregunta anterior en la pila   desbordamiento pidió un método suave de generar representaciones de serie de constantes de enumeración sin mucha reescritura propensa a errores.

Link

Mi respuesta a esa pregunta mostró cómo aplicar algo de magia preprocesador le permite definir su enumeración como esta (por ejemplo) ...;

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

... Con la ventaja de que la expansión macro no sólo define la enumeración (en un archivo .h), también define una matriz de coincidencia de cadenas (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.

46

Una cosa a tener en cuenta cuando se está utilizando el token-goma ('## ') o stringizing (' #') preprocesamiento operadores es que usted tiene que utilizar un nivel extra de indirección para que funcionen correctamente en todos los casos.

Si no lo hace, y los artículos pasados ​​al operador de red en pegar las macros son ellos mismos, obtendrá resultados que son probablemente no lo que 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 
+1

Para obtener una explicación de este comportamiento de preprocesador, consulte http://stackoverflow.com/questions/8231966/why-do-i-need-double-layer-of-indirection-for-macros –

+0

@MichaelBurr estaba leyendo su respuesta y tengo una duda. ¿Cómo es que __LINE__ está imprimiendo el número de línea? –

+2

@AbhimanyuAryan: No estoy seguro de si esto es lo que estás preguntando, pero '__LINE__' es un nombre de macro especial que es reemplazado por el preprocesador con el número de línea actual del archivo fuente. –

1

Un uso importante en la mueca de dolor:

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

Al definir Descripción bit de registro que hacer lo siguiente:

#define ADDR_LEFTSHIFT       0 

#define ADDR_WIDTH        7 

Y durante el uso de BITFMASK, basta con utilizar:

BITFMASK(ADDR) 
Cuestiones relacionadas