如何与C预处理器连接两次并展开一个宏,如“arg ## _# #宏”中所示?

时间:2022-11-25 10:53:45

I am trying to write a program where the names of some functions are dependent on the value of a certain macro variable with a macro like this:

我正在编写一个程序,其中一些函数的名称依赖于某个宏变量的值,它的宏如下:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Unfortunately, the macro NAME() turns that into

不幸的是,宏名()将其转换为

int some_function_VARIABLE(int a);

rather than

而不是

int some_function_3(int a);

so this is clearly the wrong way to go about it. Fortunately, the number of different possible values for VARIABLE is small so I can simply do an #if VARIABLE == n and list all the cases separately, but I was wondering if there is a clever way to do it.

这显然是错误的。幸运的是,变量的不同可能值的数量很小,所以我可以简单地做一个#if变量= n,然后分别列出所有的情况,但是我想知道是否有一种聪明的方法可以做到这一点。

2 个解决方案

#1


191  

Standard C Preprocessor

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Two levels of indirection

In a comment to another answer, Cade Roux asked why this needs two levels of indirection. The flippant answer is because that's how the standard requires it to work; you tend to find you need the equivalent trick with the stringizing operator too.

在对另一个答案的评论中,凯德·鲁克斯问为什么这需要两级间接。轻率的回答是因为这就是标准要求它工作的方式;你会发现你也需要使用字符串运算符。

Section 6.10.3 of the C99 standard covers 'macro replacement', and 6.10.3.1 covers 'argument substitution'.

C99标准第6.10.3节包含“宏观替代”,6.10.3.1节包含“参数替代”。

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

在确定了调用类似函数的宏的参数之后,将进行参数替换。替换列表中的参数,除非前面有#或##预处理标记,或者后面有##预处理标记(请参阅下面),在其中包含的所有宏被展开之后,将被相应的参数替换。在被替换之前,每个参数的预处理令牌被完全宏替换,就像它们形成了预处理文件的其余部分一样;没有其他预处理标记可用。

In the invocation NAME(mine), the argument is 'mine'; it is fully expanded to 'mine'; it is then substituted into the replacement string:

在调用名(my)中,参数是'mine';它完全扩展为“我的”;然后将其替换为替换字符串:

EVALUATOR(mine, VARIABLE)

Now the macro EVALUATOR is discovered, and the arguments are isolated as 'mine' and 'VARIABLE'; the latter is then fully expanded to '3', and substituted into the replacement string:

现在发现了宏求值器,参数被隔离为'mine'和'VARIABLE';然后将后者完全扩展为“3”,并替换为替换字符串:

PASTER(mine, 3)

The operation of this is covered by other rules (6.10.3.3 'The ## operator'):

此操作适用于其他规则(6.10.3.3“#操作员”):

If, in the replacement list of a function-like macro, a parameter is immediately preceded or followed by a ## preprocessing token, the parameter is replaced by the corresponding argument’s preprocessing token sequence; [...]

如果在类似函数的宏的替换列表中,一个参数的前面或后面紧跟着一个##预处理令牌,那么该参数将被相应的参数的预处理令牌序列所取代;[…]

For both object-like and function-like macro invocations, before the replacement list is reexamined for more macro names to replace, each instance of a ## preprocessing token in the replacement list (not from an argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing token.

对于类对象和类函数的宏调用,在重新检查替换列表以寻找更多的宏名称之前,替换列表中的##预处理标记(不是来自参数)的每个实例都将被删除,前面的预处理标记将与下面的预处理标记连接。

So, the replacement list contains x followed by ## and also ## followed by y; so we have:

因此,替换列表包含x,后面跟着##,还有## #,后面跟着y;所以我们有:

mine ## _ ## 3

and eliminating the ## tokens and concatenating the tokens on either side combines 'mine' with '_' and '3' to yield:

去掉##标记并将标记连接到任何一边,将“mine”和“_”和“3”组合在一起:

mine_3

This is the desired result.

这是期望的结果。


If we look at the original question, the code was (adapted to use 'mine' instead of 'some_function'):

如果我们看一下原始的问题,代码是(使用“mine”而不是“some_function”):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

The argument to NAME is clearly 'mine' and that is fully expanded.
Following the rules of 6.10.3.3, we find:

要命名的参数显然是“我的”,这是完全展开的。根据6.10.3.3的规则,我们发现:

mine ## _ ## VARIABLE

which, when the ## operators are eliminated, maps to:

当##操作符被删除时,它映射到:

mine_VARIABLE

exactly as reported in the question.

正如问题中提到的。


Traditional C Preprocessor

Robert Rüger asks:

罗伯特·鲁格问道:

Is there any way do to this with the traditional C preprocessor which does not have the token pasting operator ##?

对于没有令牌粘贴操作符## #的传统C预处理器,有什么方法可以做到这一点吗?

Maybe, and maybe not — it depends on the preprocessor. One of the advantages of the standard preprocessor is that it has this facility which works reliably, whereas there were different implementations for pre-standard preprocessors. One requirement is that when the preprocessor replaces a comment, it does not generate a space as the ANSI preprocessor is required to do. The GCC (6.3.0) C Preprocessor meets this requirement; the Clang preprocessor from XCode 8.2.1 does not.

可能,也可能不是,这取决于预处理器。标准预处理器的优点之一是它具有可靠工作的功能,而预标准预处理器有不同的实现。一个要求是,当预处理器替换注释时,它不会像ANSI预处理器那样生成空间。GCC (6.3.0) C预处理器满足这一要求;XCode 8.2.1中的Clang预处理器没有。

When it works, this does the job (x-paste.c):

当它工作的时候,这个工作(x- pastec .c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Note that there isn't a space between fun, and VARIABLE — that is important because if present, it is copied to the output, and you end up with mine_ 3 as the name, which is not syntactically valid, of course. (Now, please can I have my hair back?)

注意,在fun和VARIABLE之间没有空格——这很重要,因为如果存在,它会被复制到输出中,最后得到mine_3作为名称,当然这在语法上是无效的。(现在,请把我的头发还给我好吗?)

With GCC 6.3.0 (running cpp -traditional x-paste.c), I get:

使用GCC 6.3.0(运行cpp -传统的x-paste.c),我得到:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

With Clang from XCode 8.2.1, I get:

通过XCode 8.2.1的Clang,我得到:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Those spaces spoil everything. I note that both preprocessors are correct; different pre-standard preprocessors exhibited both behaviours, which made token pasting an extremely annoying and unreliable process when trying to port code. The standard with the ## notation radically simplifies that.

这些空间破坏一切。我注意到两个预处理器都是正确的;不同的预标准预处理器显示了这两种行为,这使得在尝试移植代码时,令牌粘贴变得非常烦人且不可靠。使用##符号的标准从根本上简化了这一点。

There might be other ways to do this. However, this does not work:

也许还有其他的方法。然而,这并不奏效:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC generates:

GCC生成:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Close, but no dice. YMMV, of course, depending on the pre-standard preprocessor that you're using. Frankly, if you're stuck with a preprocessor that is not cooperating, it would probably be simpler to arrange to use a standard C preprocessor in place of the pre-standard one (there is usually a way to configure the compiler appropriately) than to spend much time trying to work out a way to do the job.

接近,但不行。当然,YMMV取决于你使用的标准前预处理器。坦率地说,如果你坚持一个预处理器,不合作,可能简单的安排使用标准C预处理器的pre-standard之一(通常有一个适当配置编译器)而不是花很多时间试图找出一种方法来做这项工作。

#2


28  

#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Honestly, you don't want to know why this works. If you know why it works, you'll become that guy at work who knows this sort of thing, and everyone will come ask you questions. =)

老实说,你不会想知道这是为什么。如果你知道它的工作原理,你就会变成一个知道这类事情的人,每个人都会来问你问题。=)

Edit: if you actually want to know why it works, I'll happily post an explanation, assuming no one beats me to it.

编辑:如果你真的想知道它为什么有效,我会很高兴地发布一个解释,假设没有人能打败我。

#1


191  

Standard C Preprocessor

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Two levels of indirection

In a comment to another answer, Cade Roux asked why this needs two levels of indirection. The flippant answer is because that's how the standard requires it to work; you tend to find you need the equivalent trick with the stringizing operator too.

在对另一个答案的评论中,凯德·鲁克斯问为什么这需要两级间接。轻率的回答是因为这就是标准要求它工作的方式;你会发现你也需要使用字符串运算符。

Section 6.10.3 of the C99 standard covers 'macro replacement', and 6.10.3.1 covers 'argument substitution'.

C99标准第6.10.3节包含“宏观替代”,6.10.3.1节包含“参数替代”。

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

在确定了调用类似函数的宏的参数之后,将进行参数替换。替换列表中的参数,除非前面有#或##预处理标记,或者后面有##预处理标记(请参阅下面),在其中包含的所有宏被展开之后,将被相应的参数替换。在被替换之前,每个参数的预处理令牌被完全宏替换,就像它们形成了预处理文件的其余部分一样;没有其他预处理标记可用。

In the invocation NAME(mine), the argument is 'mine'; it is fully expanded to 'mine'; it is then substituted into the replacement string:

在调用名(my)中,参数是'mine';它完全扩展为“我的”;然后将其替换为替换字符串:

EVALUATOR(mine, VARIABLE)

Now the macro EVALUATOR is discovered, and the arguments are isolated as 'mine' and 'VARIABLE'; the latter is then fully expanded to '3', and substituted into the replacement string:

现在发现了宏求值器,参数被隔离为'mine'和'VARIABLE';然后将后者完全扩展为“3”,并替换为替换字符串:

PASTER(mine, 3)

The operation of this is covered by other rules (6.10.3.3 'The ## operator'):

此操作适用于其他规则(6.10.3.3“#操作员”):

If, in the replacement list of a function-like macro, a parameter is immediately preceded or followed by a ## preprocessing token, the parameter is replaced by the corresponding argument’s preprocessing token sequence; [...]

如果在类似函数的宏的替换列表中,一个参数的前面或后面紧跟着一个##预处理令牌,那么该参数将被相应的参数的预处理令牌序列所取代;[…]

For both object-like and function-like macro invocations, before the replacement list is reexamined for more macro names to replace, each instance of a ## preprocessing token in the replacement list (not from an argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing token.

对于类对象和类函数的宏调用,在重新检查替换列表以寻找更多的宏名称之前,替换列表中的##预处理标记(不是来自参数)的每个实例都将被删除,前面的预处理标记将与下面的预处理标记连接。

So, the replacement list contains x followed by ## and also ## followed by y; so we have:

因此,替换列表包含x,后面跟着##,还有## #,后面跟着y;所以我们有:

mine ## _ ## 3

and eliminating the ## tokens and concatenating the tokens on either side combines 'mine' with '_' and '3' to yield:

去掉##标记并将标记连接到任何一边,将“mine”和“_”和“3”组合在一起:

mine_3

This is the desired result.

这是期望的结果。


If we look at the original question, the code was (adapted to use 'mine' instead of 'some_function'):

如果我们看一下原始的问题,代码是(使用“mine”而不是“some_function”):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

The argument to NAME is clearly 'mine' and that is fully expanded.
Following the rules of 6.10.3.3, we find:

要命名的参数显然是“我的”,这是完全展开的。根据6.10.3.3的规则,我们发现:

mine ## _ ## VARIABLE

which, when the ## operators are eliminated, maps to:

当##操作符被删除时,它映射到:

mine_VARIABLE

exactly as reported in the question.

正如问题中提到的。


Traditional C Preprocessor

Robert Rüger asks:

罗伯特·鲁格问道:

Is there any way do to this with the traditional C preprocessor which does not have the token pasting operator ##?

对于没有令牌粘贴操作符## #的传统C预处理器,有什么方法可以做到这一点吗?

Maybe, and maybe not — it depends on the preprocessor. One of the advantages of the standard preprocessor is that it has this facility which works reliably, whereas there were different implementations for pre-standard preprocessors. One requirement is that when the preprocessor replaces a comment, it does not generate a space as the ANSI preprocessor is required to do. The GCC (6.3.0) C Preprocessor meets this requirement; the Clang preprocessor from XCode 8.2.1 does not.

可能,也可能不是,这取决于预处理器。标准预处理器的优点之一是它具有可靠工作的功能,而预标准预处理器有不同的实现。一个要求是,当预处理器替换注释时,它不会像ANSI预处理器那样生成空间。GCC (6.3.0) C预处理器满足这一要求;XCode 8.2.1中的Clang预处理器没有。

When it works, this does the job (x-paste.c):

当它工作的时候,这个工作(x- pastec .c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Note that there isn't a space between fun, and VARIABLE — that is important because if present, it is copied to the output, and you end up with mine_ 3 as the name, which is not syntactically valid, of course. (Now, please can I have my hair back?)

注意,在fun和VARIABLE之间没有空格——这很重要,因为如果存在,它会被复制到输出中,最后得到mine_3作为名称,当然这在语法上是无效的。(现在,请把我的头发还给我好吗?)

With GCC 6.3.0 (running cpp -traditional x-paste.c), I get:

使用GCC 6.3.0(运行cpp -传统的x-paste.c),我得到:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

With Clang from XCode 8.2.1, I get:

通过XCode 8.2.1的Clang,我得到:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Those spaces spoil everything. I note that both preprocessors are correct; different pre-standard preprocessors exhibited both behaviours, which made token pasting an extremely annoying and unreliable process when trying to port code. The standard with the ## notation radically simplifies that.

这些空间破坏一切。我注意到两个预处理器都是正确的;不同的预标准预处理器显示了这两种行为,这使得在尝试移植代码时,令牌粘贴变得非常烦人且不可靠。使用##符号的标准从根本上简化了这一点。

There might be other ways to do this. However, this does not work:

也许还有其他的方法。然而,这并不奏效:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC generates:

GCC生成:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Close, but no dice. YMMV, of course, depending on the pre-standard preprocessor that you're using. Frankly, if you're stuck with a preprocessor that is not cooperating, it would probably be simpler to arrange to use a standard C preprocessor in place of the pre-standard one (there is usually a way to configure the compiler appropriately) than to spend much time trying to work out a way to do the job.

接近,但不行。当然,YMMV取决于你使用的标准前预处理器。坦率地说,如果你坚持一个预处理器,不合作,可能简单的安排使用标准C预处理器的pre-standard之一(通常有一个适当配置编译器)而不是花很多时间试图找出一种方法来做这项工作。

#2


28  

#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Honestly, you don't want to know why this works. If you know why it works, you'll become that guy at work who knows this sort of thing, and everyone will come ask you questions. =)

老实说,你不会想知道这是为什么。如果你知道它的工作原理,你就会变成一个知道这类事情的人,每个人都会来问你问题。=)

Edit: if you actually want to know why it works, I'll happily post an explanation, assuming no one beats me to it.

编辑:如果你真的想知道它为什么有效,我会很高兴地发布一个解释,假设没有人能打败我。