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 supe antes de mis intentos anteriores de aprender C, es el ##
operador del preprocesador. Según K&R:
El operador del preprocesador
##
proporciona una forma de concatenar argumentos reales durante la expansión de macros. Si un parámetro en el texto de reemplazo es adyacente a a##
, el parámetro se reemplaza por el argumento real, el##
espacio en blanco y los alrededores se eliminan y el resultado se vuelve a escanear. Por ejemplo, la macropaste
concatena sus dos argumentos:
#define paste(front, back) front ## back
entonces
paste(name, 1)
crea el tokenname1
.
¿Cómo y por qué alguien usaría esto en el mundo real? ¿Cuáles son ejemplos prácticos de su uso y hay trampas a considerar?
std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__);
y construir implícitamente toda la cadena a la vez.Una cosa que debe tener en cuenta cuando usa los operadores de preprocesamiento token-paste ('
##
') o stringizing ('#
') es que debe usar un nivel adicional de indirección para que funcionen correctamente en todos los casos.Si no hace esto y los elementos que se pasan al operador de pegado de tokens son macros en sí mismos, 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
fuente
__LINE__
es un nombre de macro especial que es reemplazado por el preprocesador con el número de línea actual del archivo fuente.Aquí hay un problema con el que me encontré al actualizar a una nueva versión de un compilador:
El uso innecesario del operador de pegado de tokens (
##
) no es portátil y puede generar espacios en blanco no deseados, advertencias o errores.Cuando el resultado del operador de pegado de tokens no es un token de preprocesador válido, el operador de pegado de tokens es innecesario y posiblemente dañino.
Por ejemplo, uno podría intentar construir cadenas literales en tiempo de compilación usando el operador de pegar tokens:
#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 más 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 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 pegado de tokens.
fuente
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. Debe asignarse la función "foo"
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:
El beneficio es no tener que escribir ambos
fn_XpmFreeAttributes
y"XpmFreeAttributes"
(y arriesgarse a escribir mal uno de ellos).fuente
Una pregunta anterior en Stack Overflow pedía un método fluido para generar representaciones de cadenas para constantes de enumeración sin mucha reescritura propensa a errores.
Enlace
Mi respuesta a esa pregunta mostró cómo la aplicación de poca magia de preprocesador le permite definir su enumeración de esta manera (por ejemplo) ...;
... Con el beneficio de que la expansión de macro no solo define la enumeración (en un archivo .h), también define una matriz de cadenas coincidente (en un archivo .c);
const char *ColorStringTable[] = { "RED", "GREEN", "BLUE" };
El nombre de la tabla de cadenas proviene de pegar el parámetro macro (es decir, Color) en StringTable usando el operador ##. Aplicaciones (¿trucos?) Como esta son donde los operadores # y ## son invaluables.
fuente
Puede usar el pegado de tokens cuando necesite concatenar parámetros de macro con algo más.
Se puede utilizar para 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; };
De manera similar, puede escribir una plantilla de función para el recorrido de la lista.
fuente
Lo uso en programas C para ayudar a hacer cumplir 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 de objetos del hombre pobre en C recta:
se expande a algo como esto:
STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst );
Esto aplica la parametrización correcta para todos los objetos "derivados" cuando lo hace:
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".
fuente
SGlib usa ## básicamente para modificar 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 denominadas como sglib_list_t_concat, y así sucesivamente.
fuente
Lo uso para una aserción de inicio en un compilador C no estándar para incrustado:
#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
fuente
##
?Lo uso para agregar prefijos personalizados a variables definidas por macros. Entonces algo como:
se expande a:
void __testframework_test_name ()
fuente
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 () e 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 se aplica: casi la gran mayoría de las convenciones de nomenclatura utilizan una subcadena común para formar los nombres. Podría utilizar nombres de funciones (como arriba) o nombres de campos, nombres de variables o casi cualquier otra cosa.
fuente
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:
fuente
Es muy útil para registrar. Tu puedes hacer:
#define LOG(msg) log_msg(__function__, ## msg)
O, si su compilador no admite function y func :
#define LOG(msg) log_msg(__file__, __line__, ## msg)
Las "funciones" anteriores registran el mensaje y muestran exactamente qué función registró un mensaje.
Es posible que mi sintaxis de C ++ no sea del todo correcta.
fuente