## 预处理器运算符有哪些应用以及需要考虑的问题?
正如我之前的许多问题中提到的,我正在使用 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 macropaste
concatenates its two arguments:
#define paste(front, back) front ## back
so
paste(name, 1)
creates the tokenname1
.
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
使用标记粘贴('
##
')或字符串化('#
')预处理运算符时需要注意的一件事是,您必须使用额外的间接级别使它们能够在所有情况下正常工作。如果您不这样做,并且传递给标记粘贴运算符的项目本身就是宏,您将得到可能不是您想要的结果:
输出:
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:
The output:
CrashRpt:使用##将宏多字节字符串转换为Unicode
CrashRpt(崩溃报告库)中一个有趣的用法如下:
这里他们想使用两字节字符串而不是一字节字符串每个字符一个字节的字符串。 这可能看起来毫无意义,但他们这样做是有充分理由的。
他们将其与另一个返回带有日期和时间的字符串的宏一起使用。
将
L
放在__ DATE __
旁边会导致编译错误。Windows:使用 ## 表示通用 Unicode 或多字节字符串
Windows 使用类似以下内容:
并且
_T
在代码中随处使用各种库,用于清理访问器和修饰符名称:
我还看到它在代码中用于定义访问器和修饰符:
同样,您可以使用相同的方法来创建任何其他类型的巧妙名称。
各种库,使用它一次进行多个变量声明:
CrashRpt: Using ## to convert macro multi-byte strings to Unicode
An interesting usage in CrashRpt (crash reporting library) is the following:
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.
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:
And
_T
is used everywhere in codeVarious libraries, using for clean accessor and modifier names:
I've also seen it used in code to define accessors and modifiers:
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:
这是我在升级到新版本的编译器时遇到的一个问题:
不必要地使用标记粘贴运算符 (
##
) 是不可移植的,并且可能会生成不需要的空格、警告或错误。当令牌粘贴运算符的结果不是有效的预处理器令牌时,令牌粘贴运算符是不必要的,而且可能有害。
例如,人们可能会尝试使用标记粘贴运算符在编译时构建字符串文字:
在某些编译器上,这将输出预期结果:
在其他编译器上,这将包括不需要的空格:
相当现代的 GCC 版本 (>= 3.3 左右)将无法编译此代码:
解决方案是在将预处理器标记连接到 C/C++ 运算符时省略标记粘贴运算符:
有关连接的 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:
On some compilers, this will output the expected result:
On other compilers, this will include undesired whitespace:
Fairly modern versions of GCC (>=3.3 or so) will fail to compile this code:
The solution is to omit the token-pasting operator when concatenating preprocessor tokens to C/C++ operators:
The GCC CPP documentation chapter on concatenation has more useful information on the token-pasting operator.
这在各种情况下都很有用,以免不必要地重复。 以下是来自 Emacs 源代码的示例。 我们想从库中加载许多函数。 函数“foo”应分配给
fn_foo
,依此类推。 我们定义以下宏:然后可以使用它:
好处是不必同时编写
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:We can then use it:
The benefit is not having to write both
fn_XpmFreeAttributes
and"XpmFreeAttributes"
(and risk misspelling one of them).上一个关于 StackOverflow 的问题要求提供一种平滑的方法来生成枚举常量的字符串表示形式,而无需进行大量容易出错的重新输入。
链接
我的该问题的答案表明,应用少量预处理器魔法可以让您像这样定义枚举(例如)...;
...宏扩展的好处不仅定义了枚举(在 .h 文件中),还定义了匹配的字符串数组(在 .c 文件中);
字符串表的名称来自使用## 运算符将宏参数(即颜色)粘贴到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) ...;
... 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);
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.
当您需要将宏参数与其他内容连接时,可以使用标记粘贴。
它可以用于模板:
在这种情况下 LINKED_LIST(int) 会给你
类似的,你可以编写一个用于列表遍历的函数模板。
You can use token pasting when you need to concatenate macro parameters with something else.
It can be used for templates:
In this case LINKED_LIST(int) would give you
Similarly you can write a function template for list traversal.
主要用途是当您有命名约定并且希望宏利用该命名约定时。 也许您有多个方法系列:image_create()、image_activate() 和 image_release() 以及 file_create()、file_activate()、file_release() 和 mobile_create()、mobile_activate() 和 mobile_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:
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.
我在 C 程序中使用它来帮助正确执行一组必须符合某种调用约定的方法的原型。 在某种程度上,这可以用于直接 C 中穷人的面向对象:
扩展到类似这样的内容:
当您执行以下操作时,这会强制对所有“派生”对象进行正确的参数化:
头文件中的上述内容等。它也很有用如果您想更改定义和/或向“对象”添加方法,则可以进行维护。
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:
expands to something like this:
This enforces correct parameterization for all "derived" objects when you do:
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".
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.
我将它用于嵌入式非标准 C 编译器上的家庭滚动断言:
I use it for a home rolled assert on a non-standard C compiler for embedded:
我用它来向宏定义的变量添加自定义前缀。 所以像:
扩展到:
I use it for adding custom prefixes to variables defined by macros. So something like:
expands to:
WinCE 中的一项重要用途:
在定义寄存器位描述时,我们执行以下操作:
在使用 BITFMASK 时,只需使用:
One important use in WinCE:
While defining register bit description we do following:
And while using BITFMASK, simply use:
这对于日志记录非常有用。 您可以执行以下操作:
或者,如果您的编译器不支持 function 和 func:
上面的“functions”记录消息并准确显示哪个函数记录了消息。
我的 C++ 语法可能不太正确。
It is very useful for logging. You can do:
Or, if your compiler doesn't support function and func:
The above "functions" logs message and shows exactly which function logged a message.
My C++ syntax might be not quite correct.