c - ##预处理器运算符的应用程序和陷阱需要考虑什么?

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

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

paste(name, 1)

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

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

13个解决方案
49 votes

当您使用令牌粘贴('##')或字符串化('#')预处理运算符时,要注意的一件事是您必须使用额外的间接级别才能在所有情况下正常工作。

如果你不这样做,并且传递给令牌粘贴操作符的项目本身就是宏,你将获得可能不是你想要的结果:

#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
Michael Burr answered 2019-09-10T01:28:27Z
46 votes

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

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

_T放在__ 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;
Brian R. Bondy answered 2019-09-10T01:27:49Z
14 votes

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

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

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

例如,有人可能会尝试使用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));

在某些编译器上,这将输出预期的结果:

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文档章节有关于令牌粘贴操作符的更多有用信息。

bk1e answered 2019-09-10T01:29:46Z
6 votes

这在各种情况下都很有用,以免不必要地重复自己。 以下是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"(并且可能会拼错其中之一)。

Vebjorn Ljosa answered 2019-09-10T01:30:27Z
4 votes

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

链接

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

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

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

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

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

Bill Forster answered 2019-09-10T01:31:20Z
2 votes

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

如果您甚至碰巧想要更改定义和/或向“对象”添加方法,那么它对于维护也很有用。

Tall Jeff answered 2019-09-10T01:32:05Z
2 votes

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

answered 2019-09-10T01:32:31Z
2 votes

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


c0m4 answered 2019-09-10T01:32:58Z
2 votes

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

它可以用于模板:

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

同样,您可以编写用于列表遍历的函数模板。

qrdl answered 2019-09-10T01:33:44Z
1 votes

我用它来为宏定义的变量添加自定义前缀。 所以类似于:

UNITTEST(test_name)

扩展为:

void __testframework_test_name ()
John Millikin answered 2019-09-10T01:34:15Z
1 votes

主要用途是您有一个命名约定,并希望您的宏利用该命名约定。 也许你有几个方法系列: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())

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

mcherm answered 2019-09-10T01:34:55Z
1 votes

WinCE中的一个重要用途:

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

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

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

在使用BITFMASK时,只需使用:

BITFMASK(ADDR)
Keshava GN answered 2019-09-10T01:35:33Z
0 votes

它对日志记录非常有用。 你可以做:

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

或者,如果您的编译器不支持function和func:

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

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

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

ya23 answered 2019-09-10T01:36:20Z
translate from https://stackoverflow.com:/questions/216875/what-are-the-applications-of-the-preprocessor-operator-and-gotchas-to-conside