2008-10-19 23 views
82

正如很多我以前提到的问题,我到K & R工作组,和我目前进入预处理。其中一个更有趣的事情—的东西我从来没有从任何我以前尝试之前就知道学习C —是##预处理程序操作。可以根据K & R:什么是##预处理操作符和陷阱要考虑的应用程序?

预处理操作者## 提供了一种方法宏膨胀期间来连接实际 参数。如果在替换文本一个 参数是相邻 到##,该参数是由 实际参数替换时, ##和周围的白色空间中 去除,并且将结果重新扫描。 例如,宏paste 会将其两个参数:

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

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

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

回答

44

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; 
+3

既然你可以连接在编译时字符串文字,你可以在创建日期表达减少'的std :: wstring的创建日期=扩大( __DATE__)L“”WIDEN(__ TIME __);`并立即隐式构建整个字符串。 – user666412 2016-02-15 17:54:19

1

我用它来添加自定义前缀由宏定义的变量。因此,像:

UNITTEST(test_name) 

扩展为:

void __testframework_test_name() 
3

,当您需要连接别的东西宏观参数您可以使用标记粘贴。

它可用于模板:

#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

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

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()) 

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

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) 

上述在头文件等,这也是维护有用的,如果你连碰巧要更改的定义和/或方法添加到“对象”。

2

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

0

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

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

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

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

上述的“功能”记录信息,准确地显示其功能记录一条消息。

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

+1

你想要做什么?如果没有“##”,它也可以工作,因为不需要将令牌粘贴到“msg”。你是否想把msg串起来?另外,__FILE__和__LINE__必须大写,而不是小写。 – bk1e 2008-10-19 20:44:16

+0

你说得对。我需要找到原始脚本来查看##是如何使用的。对我感到羞耻,今天没有饼干! – ya23 2008-12-12 09:59:59

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在令牌粘贴操作符上有更多有用的信息。

+0

谢谢 - 我没有意识到这一点(但后来我不太用这些预处理操作符...)。 – 2008-10-20 01:12:48

+3

由于某种原因,它被称为“令牌粘贴”操作符 - 当您完成时,意图是以单个令牌结束。很好的写作。 – 2009-04-30 18:17:49

2

我用它为家庭推出断言在非标准的C编译器嵌入:

 


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

 
4

在堆栈中的前一个问题 溢出问生成枚举常量字符串表示的平滑方法没有很多容易出错的重新输入。

Link

我回答这个问题,展示了如何运用小魔预处理器可以让你像这样定义你的枚举(例如)...;

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

...随着益处,即宏扩展不仅定义枚举(在.h文件中),它也定义串的匹配阵列(在.c文件);

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

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

46

当您使用令牌贴('##')或字符串('#')预处理操作符时,需要注意的一件事是您必须使用额外的间接级别以使它们正常工作所有情况。

如果你不这样做,并传递给标记粘贴操作的项目是宏自己,你会得到的结果是可能不是你想要什么:

#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

在WinCE的一个重要用途:

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

在定义寄存器位描述我们做以下:

#define ADDR_LEFTSHIFT       0 

#define ADDR_WIDTH        7 

,同时使用BITFMASK,只需使用:

BITFMASK(ADDR) 
相关问题