Joh*_*udy 86 c kernighan-and-ritchie stringification c-preprocessor
正如我之前的许多问题所述,我正在通过K&R工作,目前正在进入预处理器.其中一个更有意思的事情 - 我之前从未尝试过的任何学习C的尝试 - 是##
预处理器操作员.根据K&R的说法:
预处理器运算符
##
提供了一种在宏扩展期间连接实际参数的方法.如果替换文本中##
的参数与a相邻,则参数将替换为实际参数,##
并删除周围的空白区域,并重新扫描结果.例如,宏paste
连接其两个参数:
#define paste(front, back) front ## back
所以
paste(name, 1)
创建令牌name1
.
如何以及为什么有人会在现实世界中使用它?它的使用的实际例子是什么,有什么需要考虑的?
Mic*_*urr 49
当您使用令牌粘贴(' ##
')或字符串化(' #
')预处理运算符时,需要注意的一点是,必须使用额外的间接级别才能在所有情况下正常工作.
如果你不这样做,并且传递给令牌粘贴操作符的项目本身就是宏,你将获得可能不是你想要的结果:
#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__)));
}
Run Code Online (Sandbox Code Playgroud)
输出:
buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)
desired result:
function_name21
Run Code Online (Sandbox Code Playgroud)
Bri*_*ndy 46
CrashRpt:使用##将宏多字节字符串转换为Unicode
CrashRpt(崩溃报告库)中的一个有趣用法如下:
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.
Run Code Online (Sandbox Code Playgroud)
在这里,他们希望使用双字节字符串而不是每字节一个字节的字符串.这可能看起来毫无意义,但他们这样做是有充分理由的.
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
Run Code Online (Sandbox Code Playgroud)
他们将它与另一个宏一起使用,该宏返回带有日期和时间的字符串.
放在L
旁边__ DATE __
会给你一个编译错误.
Windows:将##用于通用Unicode或多字节字符串
Windows使用类似以下内容:
#ifdef _UNICODE
#define _T(x) L ## x
#else
#define _T(x) x
#endif
Run Code Online (Sandbox Code Playgroud)
并且_T
在代码中无处不在
各种库,用于清洁访问器和修饰符名称:
我也看到它在代码中用于定义访问器和修饰符:
#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)
Run Code Online (Sandbox Code Playgroud)
同样,您可以将此相同方法用于任何其他类型的聪明名称创建.
各种库,使用它一次制作多个变量声明:
#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;
Run Code Online (Sandbox Code Playgroud)
bk1*_*k1e 14
这是我在升级到新版本的编译器时遇到的问题:
不必要地使用令牌粘贴operator(##
)是不可移植的,可能会产生不需要的空格,警告或错误.
当令牌粘贴操作符的结果不是有效的预处理器令牌时,令牌粘贴操作符是不必要的并且可能是有害的.
例如,有人可能会尝试使用token-pasting运算符在编译时构建字符串文字:
#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));
Run Code Online (Sandbox Code Playgroud)
在某些编译器上,这将输出预期的结果:
1+2 std::vector
Run Code Online (Sandbox Code Playgroud)
在其他编译器上,这将包括不需要的空格:
1 + 2 std :: vector
Run Code Online (Sandbox Code Playgroud)
相当现代的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
Run Code Online (Sandbox Code Playgroud)
解决方案是在将预处理程序令牌连接到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));
Run Code Online (Sandbox Code Playgroud)
关于连接的GCC CPP文档章节有关于令牌粘贴操作符的更多有用信息.
这在各种情况下都很有用,以免不必要地重复自己.以下是Emacs源代码中的示例.我们想从库中加载许多函数.应将函数"foo"赋值给fn_foo
,等等.我们定义以下宏:
#define LOAD_IMGLIB_FN(lib,func) { \
fn_##func = (void *) GetProcAddress (lib, #func); \
if (!fn_##func) return 0; \
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以使用它:
LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);
Run Code Online (Sandbox Code Playgroud)
好处是不必写都fn_XpmFreeAttributes
和"XpmFreeAttributes"
(和风险拼错其中之一).