如何与 C 预处理器连接两次并展开宏,如“arg ## _ ## MACRO”?
我正在尝试编写一个程序,其中某些函数的名称取决于某个具有如下宏的宏变量的值:
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
int NAME(some_function)(int a);
不幸的是,宏 NAME()
将其转换为
int some_function_VARIABLE(int a);
而不是
int some_function_3(int a);
这样显然这是错误的做法。幸运的是,VARIABLE 的不同可能值的数量很少,因此我可以简单地执行 #if VARIABLE == n
并分别列出所有情况,但有没有一种聪明的方法来做到这一点?
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 is there is a clever way to do it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
标准 C 预处理器
两级间接
在对另一个答案的评论中, Cade Roux 询问为什么这需要两级间接。轻率的回答是因为这就是标准要求它工作的方式;您往往会发现您也需要字符串化运算符的等效技巧。
C99 标准的第 6.10.3 节涵盖“宏替换”,第 6.10.3.1 节涵盖“参数替换”。
在调用
NAME(mine)
中,参数是 'mine';它完全扩展到“我的”;然后将其替换为替换字符串:现在发现了宏 EVALUATOR,并且参数被隔离为“我的”和“变量”;然后后者完全扩展为“3”,并替换为替换字符串:
此操作由其他规则涵盖(6.10.3.3“## 运算符”):
因此,替换列表包含
x
后跟##
以及##
后跟y
;所以我们有:消除
##
标记并将两侧的标记连接起来,将“我的”与“_”和“3”组合起来,得到:这是所需的结果。
如果我们看一下原来的问题,代码是(调整为使用“我的”而不是“some_function”):
NAME 的参数显然是“我的”,并且已完全扩展。
遵循 6.10.3.3 的规则,我们发现:
当消除
##
运算符时,映射到:与问题中报告的完全相同。
传统 C 预处理器
Robert Rüger 询问:
也许,也许不是——这取决于预处理器。标准预处理器的优点之一是它具有可靠工作的功能,而预标准预处理器有不同的实现。一项要求是,当预处理器替换注释时,它不会像 ANSI 预处理器所要求的那样生成空格。 GCC (6.3.0) C 预处理器满足此要求; XCode 8.2.1 中的 Clang 预处理器没有。
当它工作时,它会完成工作(
x-paste.c
):请注意,
fun,
和VARIABLE
之间没有空格— 这很重要,因为如果存在,它会被复制到输出,并且最终会得到mine_ 3
作为名称,当然,这在语法上是无效的。 (现在,我可以把头发弄回来吗?)使用 GCC 6.3.0(运行
cpp -traditional x-paste.c
),我得到:使用 XCode 8.2.1 中的 Clang,我得到:
这些空间破坏了一切。我注意到两个预处理器都是正确的;不同的预标准预处理器表现出这两种行为,这使得在尝试移植代码时令牌粘贴成为一个极其烦人且不可靠的过程。带有
##
表示法的标准从根本上简化了这一点。可能还有其他方法可以做到这一点。然而,这不起作用:
GCC 生成:
关闭,但没有骰子。 YMMV,当然,取决于您使用的预标准预处理器。坦率地说,如果您遇到不合作的预处理器,那么安排使用标准 C 预处理器代替预标准预处理器可能会比使用标准 C 预处理器更简单(通常有一种方法可以适当配置编译器)。花费大量时间试图找出完成这项工作的方法。
Standard C Preprocessor
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'.
In the invocation
NAME(mine)
, the argument is 'mine'; it is fully expanded to 'mine'; it is then substituted into the replacement string: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:
The operation of this is covered by other rules (6.10.3.3 'The ## operator'):
So, the replacement list contains
x
followed by##
and also##
followed byy
; so we have:and eliminating the
##
tokens and concatenating the tokens on either side combines 'mine' with '_' and '3' to yield:This is the desired result.
If we look at the original question, the code was (adapted to use 'mine' instead of 'some_function'):
The argument to NAME is clearly 'mine' and that is fully expanded.
Following the rules of 6.10.3.3, we find:
which, when the
##
operators are eliminated, maps to:exactly as reported in the question.
Traditional C Preprocessor
Robert Rüger asks:
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.
When it works, this does the job (
x-paste.c
):Note that there isn't a space between
fun,
andVARIABLE
— that is important because if present, it is copied to the output, and you end up withmine_ 3
as the name, which is not syntactically valid, of course. (Now, please can I have my hair back?)With GCC 6.3.0 (running
cpp -traditional x-paste.c
), I get:With Clang from XCode 8.2.1, I get:
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:
GCC generates:
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.
使用:
老实说,你不想知道为什么会这样。如果你知道它为什么起作用,你就会成为工作中知道这类事情的人,每个人都会来问你问题。 =)
Use:
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. =)
EVALUATOR 两步模式的简单英语解释
我还没有完全理解 C 标准的每个单词,但我认为这是一个合理的工作模型,说明了如何乔纳森·莱弗勒的回答有效,解释得更详细一些。如果我的理解不正确,请告诉我,希望有一个最小的例子可以打破我的理论。
出于我们的目的,我们可以将宏扩展视为分三个步骤进行:(
A ## B
) 或字符串化 (#A
) 的一部分,则它们将完全按照宏调用中给出的字符串进行替换,而无需正在扩大无间接的分步示例
main.c
中生成的宏,并将其扩展为:
we get:
因为:
第 1 步:
Y
是CAT
的宏参数。x
出现在字符串化pref_ ## x
中。因此,Y
按原样粘贴,不进行扩展,给出:步骤 2:发生串联,我们剩下:
步骤 3:发生任何进一步的宏替换。但
pref_Y
不是任何已知的宏,因此将其保留。我们可以通过实际向
pref_Y
添加定义来证实这一理论:现在的结果是:
因为在上面的第 3 步中,
pref_Y
现在被定义为宏,因此展开。间接的分步示例
如果我们使用两步模式:
我们会得到:
步骤 1:评估
CAT
。CAT(x)
被定义为CAT2(x)
,因此定义中CAT
的参数x
并不出现在字符串化中:字符串化仅在CAT2
扩展后发生,这在本步骤中看不到。因此,
Y
在被替换之前已完全扩展,经历步骤 1、2 和 3,我们在这里省略这些步骤,因为它很容易扩展为a
。因此,我们将a
放入CAT2(x)
中,给出:步骤 2:无需进行字符串化
步骤 3:展开所有现有宏。我们有宏
CAT2(a)
,因此我们继续扩展它。步骤3.1:
CAT2
的参数x
出现在字符串化pref_## x
中。因此,按原样粘贴输入字符串a
,给出:步骤 3.2:字符串化:
步骤 3:展开任何其他宏。
pref_a
不是任何宏,所以我们完成了。GCC 参数预扫描文档
GCC 关于此事的文档也值得一读:https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html
奖励:这些规则如何防止嵌套调用变得无限
现在考虑:
这扩展了to:
而不是无限。
让我们分解一下:
第 1 步:使用参数
x = f(a)
调用外部f
。在
f
的定义中,参数x
不是f< 的定义
(x + 1)
中串联的一部分/代码>。因此,在替换之前首先要完全扩展。步骤1.1.:我们根据步骤1、2和3完全展开参数
x = f(1)
,给出x = (a + 1)
。现在回到第 1 步,我们采用等于
(a + 1)
的完全扩展的x
参数,并将其放入f
的定义中,给出:第 2 步和第 3 步:没有发生太多事情,因为我们没有字符串化,也没有更多的宏可以扩展。
Plain-English explanation of the
EVALUATOR
two-step patternI haven't fully understood every word of the C standard, but I think this is a reasonable working model for how the solution shown in Jonathan Leffler's answer works, explained a little more verbosely. Let me know if my understanding is incorrect, hopefully with a minimal example that breaks my theory.
For our purposes, we can think of macro expansion as happening in three steps:
A ## B
) or stringification (#A
), they are replaced exactly as the string given on the macro call, without being expandedStep-by-step example without indirection
main.c
and expand it with:
we get:
because:
Step 1:
Y
is a the macro argument ofCAT
.x
appears in a stringificationpref_ ## x
. Therefore,Y
gets pasted as is without expansion giving:Step 2: concatenation happens and we are left with:
Step 3: any further macro replacement happens. But
pref_Y
is not any known macro, so it is left alone.We can confirm this theory by actually adding a definition to
pref_Y
:and now the result would be:
because on Step 3 above
pref_Y
is now defined as a macro, and therefore expands.Step-by-step example with indirection
If we use the two step pattern however:
we get:
Step 1:
CAT
is evaluated.CAT(x)
is defined asCAT2(x)
, so argumentx
ofCAT
at the definition does not appear in a stringification: the stringification only happens afterCAT2
is expanded, which is not seen in this step.Therefore,
Y
is fully expanded before being replaced, going through steps 1, 2, and 3, which we omit here because it trivially expands toa
. So we puta
inCAT2(x)
giving:Step 2: there is no stringification to be done
Step 3: expand all existing macros. We have the macro
CAT2(a)
and so we go on to expand that.Step 3.1: the argument
x
ofCAT2
appears in a stringificationpref_ ## x
. Therefore, paste the input stringa
as is, giving:Step 3.2: stringify:
Step 3: expand any further macros.
pref_a
is not any macro, so we are done.GCC argument prescan documentation
GCC's documentation on the matter is also worth a read: https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html
Bonus: how those rules prevent nested calls from going infinite
Now consider:
which expands to:
instead of going infinite.
Let's break it down:
Step 1: the outer
f
is called with argumentx = f(a)
.In the definition of
f
, the argumentx
is not part of a concatenation in the definition(x + 1)
off
. Therefore it is first fully expanded before being replaced.Step 1.1.: we fully expand the argument
x = f(1)
according to steps 1, 2, and 3, givingx = (a + 1)
.Now back in Step 1, we take that fully expanded
x
argument equaling(a + 1)
, and put it inside the definition off
giving:Steps 2 and 3: not much happens, because we have no stringification and no more macros to expand.