##预处理器运算符和陷阱的应用有哪些应用?

时间:2008-10-19 19:51:26

标签: c c-preprocessor kernighan-and-ritchie stringification

正如我之前的许多问题中提到的,我正在通过K& R工作,目前正在进入预处理器。其中一个更有趣的事情 - 我以前从未学过的任何一个学习C的尝试 - 是##预处理器操作符。根据K& R:

  

预处理程序运算符##   提供了一种连接实际的方法   宏观扩张期间的争论。如果一个   替换文本中的参数是   与##相邻,参数为   取而代之的是实际的论点   ##和周围的空白区域   删除,并重新扫描结果。   例如,宏paste   连接它的两个论点:

     

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

     

所以paste(name, 1)会创建令牌   name1

如何以及为什么有人会在现实世界中使用它?它的使用的实际例子是什么,有什么需要考虑的?

13 个答案:

答案 0 :(得分: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__)));
}

输出:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21

答案 1 :(得分: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.

在这里,他们想要使用双字节字符串而不是每字节一个字节的字符串。这可能看起来毫无意义,但他们这样做是有充分理由的。

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

他们将它与另一个宏一起使用,该宏返回带有日期和时间的字符串。

L放在__ DATE __旁边会出现编译错误。


Windows:将##用于通用Unicode或多字节字符串

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)

同样,您可以将此相同方法用于任何其他类型的聪明名称创建。


各种库,使用它一次制作多个变量声明:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;

答案 2 :(得分:14)

这是我在升级到新版本的编译器时遇到的问题:

不必要地使用令牌粘贴操作符(##)是不可移植的,可能会产生意外的空格,警告或错误。

当令牌粘贴操作符的结果不是有效的预处理器令牌时,令牌粘贴操作符是不必要的并且可能有害。

例如,可以尝试使用令牌粘贴操作符在编译时构建字符串文字:

#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 documentation chapter on concatenation有关于令牌粘贴操作符的更多有用信息。

答案 3 :(得分:6)

这在各种情况下都很有用,以免不必要地重复自己。以下是Emacs源代码中的示例。我们想从库中加载许多函数。应将函数“foo”分配给fn_foo,依此类推。我们定义以下宏:

#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"(并且可能会拼错其中一个)。

答案 4 :(得分:4)

关于Stack Overflow的上一个问题要求一个平滑的方法来生成枚举常量的字符串表示,而不需要很多容易出错的重新输入。

Link

我对这个问题的回答显示了如何应用很少的预处理器魔法让你像这样定义你的枚举(例如)......;

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

...宏扩展不仅定义了枚举(在.h文件中),还定义了匹配的字符串数组(在.c文件中);

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

字符串表的名称来自于使用##运算符将宏参数(即Color)粘贴到StringTable。像这样的应用程序(技巧?)是#和##运算符非常宝贵的地方。

答案 5 :(得分:2)

当您需要将宏参数与其他内容连接时,可以使用标记粘贴。

它可用于模板:

#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;
};

类似地,您可以为列表遍历编写函数模板。

答案 6 :(得分:2)

我在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 )

上面的头文件等等。如果您甚至想要更改定义和/或向“对象”添加方法,它对维护也很有用。

答案 7 :(得分:2)

SGlib使用##来基本上捏造C中的模板。因为没有函数重载,所以##用于将类型名称粘贴到生成的函数的名称中。如果我有一个名为list_t的列表类型,那么我将获得名为sglib_list_t_concat的函数,依此类推。

答案 8 :(得分:2)

我将它用于嵌入式非标准C编译器上的home roll assert:



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 


答案 9 :(得分:1)

我用它来为宏定义的变量添加自定义前缀。如下所示:

UNITTEST(test_name)

扩展为:

void __testframework_test_name ()

答案 10 :(得分:1)

主要用途是当您有命名约定并且希望宏利用该命名约定时。也许你有几个方法系列:image_create(),image_activate()和image_release()也是file_create(),file_activate(),file_release()和mobile_create(),mobile_activate()和mobile_release()。

您可以编写一个用于处理对象生命周期的宏:

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

当然,一种“对象的最小版本”并不是唯一适用的命名约定 - 几乎绝大多数命名约定都使用公共子字符串来形成名称。它可以是函数名称(如上所述),或字段名称,变量名称或大多数其他内容。

答案 11 :(得分:1)

WinCE中的一个重要用途:

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

在定义寄存器位描述时,我们执行以下操作:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

使用BITFMASK时,只需使用:

BITFMASK(ADDR)

答案 12 :(得分:0)

对于日志记录非常有用。你可以这样做:

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

或者,如果您的编译器不支持功能功能

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

上面的“函数”会记录消息,并准确显示记录消息的函数。

我的C ++语法可能不太正确。