##预处理器运算符的应用程序有哪些,需要考虑?
正如我以前的许多问题所提到的,我正在通过K&R工作,目前正在进行预处理。 其中一个比较有意思的事情就是##
预处理器操作符,这是我之前从来没有学过的东西,我从来没有知道过。 根据K&R:
预处理运算符
##
提供了在macros扩展期间连接实际参数的方法。 如果replace文本中的参数与##
相邻,则将参数replace为实际参数,将删除##
和周围的空白区域,并重新扫描结果。 例如,macrospaste
连接它的两个参数:
#define paste(front, back) front ## back
所以
paste(name, 1)
创build令牌name1
。
如何以及为什么有人在现实世界中使用它? 什么是它的使用的实际例子,有什么需要考虑的?
CrashRpt:使用##将macros多字节string转换为Unicode
CrashRpt(崩溃报告库)中一个有趣的用法如下:
#define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) //Note you need a WIDEN2 so that __DATE__ will evaluate first.
在这里,他们想要使用一个两字节的string,而不是每字符一个字节的string。 这可能看起来是毫无意义的,但是他们这样做是有原因的。
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
他们与另一个使用date和时间返回string的macros一起使用它。
把L
放在__ DATE __
旁边会给你一个编译错误。
Windows:对通用Unicode或多字节string使用##
Windows使用如下所示的内容:
#ifdef _UNICODE #define _T(x) L ## x #else #define _T(x) x #endif
而且_T
在代码中无处不在
各种库,使用干净的访问器和修饰符名称:
我也看到它在代码中用来定义访问器和修饰符:
#define MYLIB_ACCESSOR(name) (Get##name) #define MYLIB_MODIFIER(name) (Set##name)
同样,你可以使用这个相同的方法来创build任何其他types的聪明的名字。
各种库,使用它来一次进行几个variables声明:
#define CREATE_3_VARS(name) name##1, name##2, name##3 int CREATE_3_VARS(myInts); myInts1 = 13; myInts2 = 19; myInts3 = 77;
在使用标记粘贴(' ##
')或者string处理(' #
')预处理操作符时要注意的一件事是,为了使它们在所有情况下都能够正常工作,必须使用额外的间接级别。
如果你不这样做,传递给令牌粘贴操作符的项目本身就是macros,那么你将得到的结果可能不是你想要的:
#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__))); }
输出:
buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21
以下是升级到新版本编译器时遇到的问题:
令牌粘贴操作符( ##
)的不必要的使用是不可移植的,可能会产生不需要的空白,警告或错误。
当令牌粘贴操作符的结果不是有效的预处理令牌时,令牌粘贴操作符是不必要的,并且可能是有害的。
例如,可以尝试在编译时使用令牌粘贴操作符来构buildstring文字:
#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));
在一些编译器上,这将输出预期的结果:
1+2 std::vector
在其他编译器上,这将包括不需要的空白:
1 + 2 std :: vector
相当现代的GCC版本(> = 3.3左右)将无法编译此代码:
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
解决方法是在将预处理标记连接到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));
关于连接的GCC CPP文档章节有关令牌粘贴操作符的更多有用信息。
这在各种情况下都是有用的,以免不必要地重复自己。 以下是Emacs源代码的一个例子。 我们想从库中加载一些函数。 函数“foo”应该分配给fn_foo
,依此类推。 我们定义下面的macros:
#define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ }
我们可以使用它:
LOAD_IMGLIB_FN (library, XpmFreeAttributes); LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer); LOAD_IMGLIB_FN (library, XpmReadFileToImage); LOAD_IMGLIB_FN (library, XImageFree);
好处是不必同时写fn_XpmFreeAttributes
和"XpmFreeAttributes"
(并冒险拼错其中之一)。
关于堆栈溢出的前一个问题要求为枚举常量生成string表示的平滑方法,而不需要大量容易出错的重新input。
链接
我对这个问题的回答显示了如何应用一些预处理器魔法,让你像这样定义你的枚举(例如)…;
ENUM_BEGIN( Color ) ENUM(RED), ENUM(GREEN), ENUM(BLUE) ENUM_END( Color )
…macros扩展不仅定义了枚举(在.h文件中),还定义了匹配的string数组(在.c文件中)。
const char *ColorStringTable[] = { "RED", "GREEN", "BLUE" };
string表的名字来自使用##运算符将macros参数(即Color)粘贴到StringTable。 像这样的应用程序(技巧?)是#和##运算符是无价的。
当需要连接macros参数和其他东西时,可以使用令牌粘贴。
它可以用于模板:
#define LINKED_LIST(A) struct list##_##A {\ A value; \ struct list##_##A *next; \ };
在这种情况下,LINKED_LIST(int)会给你
struct list_int { int value; struct list_int *next; };
同样,你可以编写一个用于列表遍历的函数模板。
我在C程序中使用它来帮助正确执行一些必须符合某种调用约定的方法的原型。 从某种意义上说,这可以用于穷人在直线C中的物体取向:
SCREEN_HANDLER( activeCall )
扩展到这样的东西:
STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst );
当你这样做的时候,这将强制所有“派生”对象的正确参数化:
SCREEN_HANDLER( activeCall ) SCREEN_HANDLER( ringingCall ) SCREEN_HANDLER( heldCall )
以上在你的头文件等等。如果你甚至碰巧想改变定义和/或向“对象”添加方法,它对于维护也是有用的。
SGlib使用##来基本上模糊 C中的模板。因为没有函数重载,所以##用于将types名称粘贴到生成函数的名称中。 如果我有一个名为list_t的列表types,那么我会得到像sglib_list_t_concat这样的函数,依此类推。
我将它用于embedded式的非标准C编译器的home roll assert:
#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
我用它来为由macros定义的variables添加自定义前缀。 所以像这样:
UNITTEST(test_name)
扩展到:
void __testframework_test_name ()
主要用途是当你有一个命名约定,并且你希望你的macros利用这个命名约定。 也许你有几个方法家族:image_create(),image_activate()和image_release()也file_create(),file_activate(),file_release()和mobile_create(),mobile_activate()和mobile_release()。
你可以写一个macros来处理对象生命周期:
#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())
当然,某种“最小版本的对象”并不是唯一适用的命名约定 – 几乎绝大多数的命名约定都使用一个公共的子string来形成名称。 它可以我函数的名称(如上),或者字段名称,variables名称或者其他任何东西。
WinCE中一个重要的用途:
#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))
在定义寄存器位描述的同时,
#define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7
而使用BITFMASK,只需使用:
BITFMASK(ADDR)
这对logging非常有用。 你可以做:
#define LOG(msg) log_msg(__function__, ## msg)
或者,如果你的编译器不支持函数和func :
#define LOG(msg) log_msg(__file__, __line__, ## msg)
上面的“函数”logging了消息并准确显示了哪个函数logging了一条消息。
我的C ++语法可能不完全正确。