## 预处理器运算符有哪些应用以及需要考虑的问题?

发布于 2024-07-06 18:35:34 字数 562 浏览 8 评论 0原文

正如我之前的许多问题中提到的,我正在使用 K&R,目前正在使用预处理器。 更有趣的事情之一是 ## 预处理器运算符,这是我之前尝试学习 C 时从未了解过的事情。 根据 K&R 的说法:

预处理器运算符## 提供了一种连接实际的方法 宏展开期间的参数。 如果一个 替换文本中的参数是 与 ## 相邻,参数为 替换为实际参数, ## 和周围的空白是 删除,并重新扫描结果。 例如,宏paste 连接它的两个参数:

#define Paste(正面,背面)正面##背面

so paste(name, 1) 创建令牌 名称1

在现实世界中,人们如何以及为什么会使用它? 它的使用有哪些实际示例?是否有需要考虑的问题?

As mentioned in many of my previous questions, I'm working through K&R, and am currently into the preprocessor. One of the more interesting things — something I never knew before from any of my prior attempts to learn C — is the ## preprocessor operator. According to K&R:

The preprocessor operator ##
provides a way to concatenate actual
arguments during macro expansion. If a
parameter in the replacement text is
adjacent to a ##, the parameter is
replaced by the actual argument, the
## and surrounding white space are
removed, and the result is re-scanned.
For example, the macro paste
concatenates its two arguments:

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

so paste(name, 1) creates the token
name1.

How and why would someone use this in the real world? What are practical examples of its use, and are there gotchas to consider?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

Bonjour°[大白 2024-07-13 18:35:35

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

如果您不这样做,并且传递给标记粘贴运算符的项目本身就是宏,您将得到可能不是您想要的结果:

#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

One thing to be aware of when you're using the token-paste ('##') or stringizing ('#') preprocessing operators is that you have to use an extra level of indirection for them to work properly in all cases.

If you don't do this and the items passed to the token-pasting operator are macros themselves, you'll get results that are probably not what you want:

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

The output:

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

desired result:
function_name21
池予 2024-07-13 18:35:35

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;

CrashRpt: Using ## to convert macro multi-byte strings to Unicode

An interesting usage in CrashRpt (crash reporting library) is the following:

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

Here they want to use a two-byte string instead of a one-byte-per-char string. This probably looks like it is really pointless, but they do it for a good reason.

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

They use it with another macro that returns a string with the date and time.

Putting L next to a __ DATE __ would give you a compiling error.


Windows: Using ## for generic Unicode or multi-byte strings

Windows uses something like the following:

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

And _T is used everywhere in code


Various libraries, using for clean accessor and modifier names:

I've also seen it used in code to define accessors and modifiers:

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

Likewise you can use this same method for any other types of clever name creation.


Various libraries, using it to make several variable declarations at once:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;
岁吢 2024-07-13 18:35:35

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

不必要地使用标记粘贴运算符 (##) 是不可移植的,并且可能会生成不需要的空格、警告或错误。

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

例如,人们可能会尝试使用标记粘贴运算符在编译时构建字符串文字:

#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 文档章节 提供了有关标记粘贴运算符的更多有用信息。

Here's a gotcha that I ran into when upgrading to a new version of a compiler:

Unnecessary use of the token-pasting operator (##) is non-portable and may generate undesired whitespace, warnings, or errors.

When the result of the token-pasting operator is not a valid preprocessor token, the token-pasting operator is unnecessary and possibly harmful.

For example, one might try to build string literals at compile time using the token-pasting operator:

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

On some compilers, this will output the expected result:

1+2 std::vector

On other compilers, this will include undesired whitespace:

1 + 2 std :: vector

Fairly modern versions of GCC (>=3.3 or so) will fail to compile this code:

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

The solution is to omit the token-pasting operator when concatenating preprocessor tokens to C/C++ operators:

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

The GCC CPP documentation chapter on concatenation has more useful information on the token-pasting operator.

浴红衣 2024-07-13 18:35:35

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

This is useful in all kinds of situations in order not to repeat yourself needlessly. The following is an example from the Emacs source code. We would like to load a number of functions from a library. The function "foo" should be assigned to fn_foo, and so on. We define the following macro:

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

We can then use it:

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

The benefit is not having to write both fn_XpmFreeAttributes and "XpmFreeAttributes" (and risk misspelling one of them).

任谁 2024-07-13 18:35:35

上一个关于 StackOverflow 的问题要求提供一种平滑的方法来生成枚举常量的字符串表示形式,而无需进行大量容易出错的重新输入。

链接

我的该问题的答案表明,应用少量预处理器魔法可以让您像这样定义枚举(例如)...;

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

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

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

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

A previous question on Stack Overflow asked for a smooth method of generating string representations for enumeration constants without a lot of error-prone retyping.

Link

My answer to that question showed how applying little preprocessor magic lets you define your enumeration like this (for example) ...;

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

... With the benefit that the macro expansion not only defines the enumeration (in a .h file), it also defines a matching array of strings (in a .c file);

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

The name of the string table comes from pasting the macro parameter (i.e. Color) to StringTable using the ## operator. Applications (tricks?) like this are where the # and ## operators are invaluable.

等待圉鍢 2024-07-13 18:35:35

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

它可以用于模板:

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

类似的,你可以编写一个用于列表遍历的函数模板。

You can use token pasting when you need to concatenate macro parameters with something else.

It can be used for templates:

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

In this case LINKED_LIST(int) would give you

struct list_int {
int value;
struct list_int *next;
};

Similarly you can write a function template for list traversal.

誰ツ都不明白 2024-07-13 18:35:35

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

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

The main use is when you have a naming convention and you want your macro to take advantage of that naming convention. Perhaps you have several families of methods: image_create(), image_activate(), and image_release() also file_create(), file_activate(), file_release(), and mobile_create(), mobile_activate() and mobile_release().

You could write a macro for handling object lifecycle:

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

Of course, a sort of "minimal version of objects" is not the only sort of naming convention this applies to -- nearly the vast majority of naming conventions make use of a common sub-string to form the names. It could me function names (as above), or field names, variable names, or most anything else.

红衣飘飘貌似仙 2024-07-13 18:35:35

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

头文件中的上述内容等。它也很有用如果您想更改定义和/或向“对象”添加方法,则可以进行维护。

I use it in C programs to help correctly enforce the prototypes for a set of methods that must conform to some sort of calling convention. In a way, this can be used for poor man's object orientation in straight C:

SCREEN_HANDLER( activeCall )

expands to something like this:

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

This enforces correct parameterization for all "derived" objects when you do:

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

the above in your header files, etc. It is also useful for maintenance if you even happen to want to change the definitions and/or add methods to the "objects".

动次打次papapa 2024-07-13 18:35:35

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

SGlib uses ## to basically fudge templates in C. Because there's no function overloading, ## is used to glue the type name into the names of the generated functions. If I had a list type called list_t, then I would get functions named like sglib_list_t_concat, and so on.

捂风挽笑 2024-07-13 18:35:35

我将它用于嵌入式非标准 C 编译器上的家庭滚动断言:



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


I use it for a home rolled assert on a non-standard C compiler for embedded:



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


萌梦深 2024-07-13 18:35:35

我用它来向宏定义的变量添加自定义前缀。 所以像:

UNITTEST(test_name)

扩展到:

void __testframework_test_name ()

I use it for adding custom prefixes to variables defined by macros. So something like:

UNITTEST(test_name)

expands to:

void __testframework_test_name ()
初见 2024-07-13 18:35:35

WinCE 中的一项重要用途:

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

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

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

在使用 BITFMASK 时,只需使用:

BITFMASK(ADDR)

One important use in WinCE:

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

While defining register bit description we do following:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

And while using BITFMASK, simply use:

BITFMASK(ADDR)
剩余の解释 2024-07-13 18:35:35

这对于日志记录非常有用。 您可以执行以下操作:

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

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

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

上面的“functions”记录消息并准确显示哪个函数记录了消息。

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

It is very useful for logging. You can do:

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

Or, if your compiler doesn't support function and func:

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

The above "functions" logs message and shows exactly which function logged a message.

My C++ syntax might be not quite correct.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文