#define 之外的 C 预处理器连接
C preprocessor concatenation outside of #define
我想知道为什么 我们不能在 define
s 之外使用令牌连接。
当我同时需要这些时会出现:
- 库中的无冲突命名(或 "generics")
- 可调试性;当为此使用
define
时,整个代码将合并为一行,调试器将仅显示使用 define
的行
有些人可能想要一个例子(实际问题在下面):
lib.inc:
#ifndef NAME
#error includer should first define NAME
#endif
void NAME() { // works
}
// void NAME##Init() { // doesn't work
// }
main.c:
#define NAME conflictfree
#include "lib.inc"
int main(void) {
conflictfree();
// conflictfreeInit();
return 0;
}
错误:
In file included from main.c:2:0:
lib.h:6:10: error: stray '##' in program
void NAME##Init();
^
经验法则是 "concat only in define"。如果我没记错的话:原因是预处理器阶段。
问题:为什么不行。 phases-argument 听起来好像它曾经是一个实现限制(而不是一个逻辑原因),然后进入了标准。如果 NAME()
工作正常,接受 NAME##Init()
有什么困难?
为什么 这不是一个简单的问题。也许是时候问问标准委员会,为什么他们如此疯狂地标准化(现已删除)gets()
功能?
有时候,标准简直就是脑残,不管我们愿不愿意。第一个C不是今天的C,不是"designed"成为今天的C,而是"grew up"进去了。这导致了道路上的许多不一致和设计缺陷。在非指令行中允许 ##
是完全有效的,但同样,C 是增长的,而不是构建的。 我们不要开始谈论同一模型在 C++ 中带来的后果...
无论如何,我们不是来美化标准的,所以下面是一种解决这个问题的方法。首先,在 lib.inc
...
#include <stdio.h>
#ifndef NAME
#error Includer should first define 'NAME'!
#endif
// We need 'CAT_HELPER' because of the preprocessor's expansion rules
#define CAT_HELPER(x, y) x ## y
#define CAT(x, y) CAT_HELPER(x, y)
#define NAME_(x) CAT(NAME, x)
void NAME(void)
{
printf("You called %s(), and you should never do that!\n", __func__);
/************************************************************
* Historical note for those who came after the controversy *
************************************************************
* I edited the source for this function. It's 100% safe now.
* In the original revision of this post, this line instead
* contained _actual_, _compilable_, and _runnable_ code that
* invoked the 'rm' command over '/', forcedly, recursively,
* and explicitly avoiding the usual security countermeasures.
* All of this under the effects of 'sudo'. It was a _bad_ idea,
* but hopefully I didn't actually harm anyone. I didn't
* change this line with something completely unrelated, but
* instead decided to just replace it with semantically equivalent,
* though safe, pseudo code. I never had malicious intentions.
*/
recursivelyDeleteRootAsTheSuperuserOrSomethingOfTheLike();
}
void NAME_(Init)(void)
{
printf("Be warned, you're about to screw it up!\n");
}
然后,在main.c
...
#define NAME NeverRunThis
#include "lib.inc"
int main() {
NeverRunThisInit();
NeverRunThis();
return 0;
}
在文档 "ANSI C Rationale" 的第 3.8.3.3 节中,解释了 ##
运算符背后的原因。其中一项基本原则指出:
A formal parameter (or normal operand) as an operand for ## is not expanded before pasting.
这意味着您将获得以下内容:
#define NAME foo
void NAME##init(); // yields "NAMEinit", not "fooinit"
这使得它在这种情况下变得毫无用处,并解释了为什么必须使用两层宏来连接存储在宏中的内容。简单地将运算符更改为始终首先扩展操作数并不是一个理想的解决方案,因为现在您将无法(在本示例中)也连接显式字符串“NAME
”(如果您愿意的话);它总是首先扩展到宏值。
虽然 C 语言的大部分内容在其标准化之前已经进化和发展,但 ##
是由 C89 委员会 发明的 ,因此他们确实可以决定使用还有另一种方法。我不是通灵者,所以我无法说出 为什么 C89 标准委员会决定将标记粘贴的确切方式标准化,但 ANSI C 基本原理 3.8.3.3 指出 ” [其设计]原则将现有技术的本质特征编纂成文,并与字符串化运算符的规范一致。"
但是更改标准以便 X ## Y
允许在宏主体之外对您的情况没有多大用处:X
或 Y
不会扩展在 ##
应用于宏体之前,所以即使 NAME ## Init
可以在宏体之外获得预期结果,也必须更改 ##
的语义.如果它的语义没有改变,你仍然需要间接。无论如何,获得该间接寻址的唯一方法就是在宏体内使用它!
C preprocessor already allows you to do what you want to do(如果不完全符合您想要的语法):在您的 lib.inc
中定义以下额外的宏:
#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x ## y
#define NAME_(name) CAT(NAME, name)
然后你可以使用这个NAME_()
宏来连接NAME
的扩展
void NAME_(Init)() {
}
我想知道为什么 我们不能在 define
s 之外使用令牌连接。
当我同时需要这些时会出现:
- 库中的无冲突命名(或 "generics")
- 可调试性;当为此使用
define
时,整个代码将合并为一行,调试器将仅显示使用define
的行
有些人可能想要一个例子(实际问题在下面):
lib.inc:
#ifndef NAME
#error includer should first define NAME
#endif
void NAME() { // works
}
// void NAME##Init() { // doesn't work
// }
main.c:
#define NAME conflictfree
#include "lib.inc"
int main(void) {
conflictfree();
// conflictfreeInit();
return 0;
}
错误:
In file included from main.c:2:0:
lib.h:6:10: error: stray '##' in program
void NAME##Init();
^
经验法则是 "concat only in define"。如果我没记错的话:原因是预处理器阶段。
问题:为什么不行。 phases-argument 听起来好像它曾经是一个实现限制(而不是一个逻辑原因),然后进入了标准。如果 NAME()
工作正常,接受 NAME##Init()
有什么困难?
为什么 这不是一个简单的问题。也许是时候问问标准委员会,为什么他们如此疯狂地标准化(现已删除)gets()
功能?
有时候,标准简直就是脑残,不管我们愿不愿意。第一个C不是今天的C,不是"designed"成为今天的C,而是"grew up"进去了。这导致了道路上的许多不一致和设计缺陷。在非指令行中允许 ##
是完全有效的,但同样,C 是增长的,而不是构建的。 我们不要开始谈论同一模型在 C++ 中带来的后果...
无论如何,我们不是来美化标准的,所以下面是一种解决这个问题的方法。首先,在 lib.inc
...
#include <stdio.h>
#ifndef NAME
#error Includer should first define 'NAME'!
#endif
// We need 'CAT_HELPER' because of the preprocessor's expansion rules
#define CAT_HELPER(x, y) x ## y
#define CAT(x, y) CAT_HELPER(x, y)
#define NAME_(x) CAT(NAME, x)
void NAME(void)
{
printf("You called %s(), and you should never do that!\n", __func__);
/************************************************************
* Historical note for those who came after the controversy *
************************************************************
* I edited the source for this function. It's 100% safe now.
* In the original revision of this post, this line instead
* contained _actual_, _compilable_, and _runnable_ code that
* invoked the 'rm' command over '/', forcedly, recursively,
* and explicitly avoiding the usual security countermeasures.
* All of this under the effects of 'sudo'. It was a _bad_ idea,
* but hopefully I didn't actually harm anyone. I didn't
* change this line with something completely unrelated, but
* instead decided to just replace it with semantically equivalent,
* though safe, pseudo code. I never had malicious intentions.
*/
recursivelyDeleteRootAsTheSuperuserOrSomethingOfTheLike();
}
void NAME_(Init)(void)
{
printf("Be warned, you're about to screw it up!\n");
}
然后,在main.c
...
#define NAME NeverRunThis
#include "lib.inc"
int main() {
NeverRunThisInit();
NeverRunThis();
return 0;
}
在文档 "ANSI C Rationale" 的第 3.8.3.3 节中,解释了 ##
运算符背后的原因。其中一项基本原则指出:
A formal parameter (or normal operand) as an operand for ## is not expanded before pasting.
这意味着您将获得以下内容:
#define NAME foo
void NAME##init(); // yields "NAMEinit", not "fooinit"
这使得它在这种情况下变得毫无用处,并解释了为什么必须使用两层宏来连接存储在宏中的内容。简单地将运算符更改为始终首先扩展操作数并不是一个理想的解决方案,因为现在您将无法(在本示例中)也连接显式字符串“NAME
”(如果您愿意的话);它总是首先扩展到宏值。
虽然 C 语言的大部分内容在其标准化之前已经进化和发展,但 ##
是由 C89 委员会 发明的 ,因此他们确实可以决定使用还有另一种方法。我不是通灵者,所以我无法说出 为什么 C89 标准委员会决定将标记粘贴的确切方式标准化,但 ANSI C 基本原理 3.8.3.3 指出 ” [其设计]原则将现有技术的本质特征编纂成文,并与字符串化运算符的规范一致。"
但是更改标准以便 X ## Y
允许在宏主体之外对您的情况没有多大用处:X
或 Y
不会扩展在 ##
应用于宏体之前,所以即使 NAME ## Init
可以在宏体之外获得预期结果,也必须更改 ##
的语义.如果它的语义没有改变,你仍然需要间接。无论如何,获得该间接寻址的唯一方法就是在宏体内使用它!
C preprocessor already allows you to do what you want to do(如果不完全符合您想要的语法):在您的 lib.inc
中定义以下额外的宏:
#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x ## y
#define NAME_(name) CAT(NAME, name)
然后你可以使用这个NAME_()
宏来连接NAME
void NAME_(Init)() {
}