如何与 C 预处理器连接两次并展开宏,如“arg ## _ ## MACRO”?

发布于 2024-08-05 21:17:54 字数 441 浏览 6 评论 0原文

我正在尝试编写一个程序,其中某些函数的名称取决于某个具有如下宏的宏变量的值:

#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 技术交流群。

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

发布评论

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

评论(3

此刻的回忆 2024-08-12 21:17:54

标准 C 预处理器

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

两级间接

在对另一个答案的评论中, Cade Roux 询问为什么这需要两级间接。轻率的回答是因为这就是标准要求它工作的方式;您往往会发现您也需要字符串化运算符的等效技巧。

C99 标准的第 6.10.3 节涵盖“宏替换”,第 6.10.3.1 节涵盖“参数替换”。

在确定了调用类函数宏的参数后,
发生参数替换。替换列表中的参数,除非前面有
### 预处理标记或后跟 ## 预处理标记(见下文),是
在其中包含的所有宏都被替换后,替换为相应的参数
扩大了。在被替换之前,每个参数的预处理标记是
完全替换宏,就好像它们构成了预处理文件的其余部分一样;没有其他
预处理令牌可用。

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

EVALUATOR(mine, VARIABLE)

现在发现了宏 EVALUATOR,并且参数被隔离为“我的”和“变量”;然后后者完全扩展为“3”,并替换为替换字符串:

PASTER(mine, 3)

此操作由其他规则涵盖(6.10.3.3“## 运算符”):

如果在类函数宏的替换列表中,参数紧接在前面
或者后面跟一个 ## 预处理标记,该参数被替换为相应的
参数的预处理标记序列; [...]

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

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

mine ## _ ## 3

消除 ## 标记并将两侧的标记连接起来,将“我的”与“_”和“3”组合起来,得到:

mine_3

这是所需的结果。


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

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

NAME(mine)

NAME 的参数显然是“我的”,并且已完全扩展。
遵循 6.10.3.3 的规则,我们发现:

mine ## _ ## VARIABLE

当消除 ## 运算符时,映射到:

mine_VARIABLE

与问题中报告的完全相同。


传统 C 预处理器

Robert Rüger 询问

对于没有标记粘贴运算符 ## 的传统 C 预处理器,有什么办法可以做到这一点吗?

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

当它工作时,它会完成工作(x-paste.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);

请注意,fun,VARIABLE 之间没有空格— 这很重要,因为如果存在,它会被复制到输出,并且最终会得到 mine_ 3 作为名称,当然,这在语法上是无效的。 (现在,我可以把头发弄回来吗?)

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

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





extern void mine_3(char *x);

使用 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);

这些空间破坏了一切。我注意到两个预处理器都是正确的;不同的预标准预处理器表现出这两种行为,这使得在尝试移植代码时令牌粘贴成为一个极其烦人且不可靠的过程。带有 ## 表示法的标准从根本上简化了这一点。

可能还有其他方法可以做到这一点。然而,这不起作用:

#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 生成:

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





extern void mine_VARIABLE(char *x);

关闭,但没有骰子。 YMMV,当然,取决于您使用的预标准预处理器。坦率地说,如果您遇到不合作的预处理器,那么安排使用标准 C 预处理器代替预标准预处理器可能会比使用标准 C 预处理器更简单(通常有一种方法可以适当配置编译器)。花费大量时间试图找出完成这项工作的方法。

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'.

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:

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:

PASTER(mine, 3)

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

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:

mine ## _ ## 3

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

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'):

#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:

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 ##?

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):

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

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

# 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:

# 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:

# 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.

千纸鹤带着心事 2024-08-12 21:17:54

使用:

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

老实说,你不想知道为什么会这样。如果你知道它为什么起作用,你就会成为工作中知道这类事情的人,每个人都会来问你问题。 =)

Use:

#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. =)

风流物 2024-08-12 21:17:54

EVALUATOR 两步模式的简单英语解释

我还没有完全理解 C 标准的每个单词,但我认为这是一个合理的工作模型,说明了如何乔纳森·莱弗勒的回答有效,解释得更详细一些。如果我的理解不正确,请告诉我,希望有一个最小的例子可以打破我的理论。

出于我们的目的,我们可以将宏扩展视为分三个步骤进行:(

  1. 预扫描)宏参数被替换:
    • 如果它们是串联 (A ## B) 或字符串化 (#A) 的一部分,则它们将完全按照宏调用中给出的字符串进行替换,而无需正在扩大
    • 否则,它们首先完全展开,然后才被替换
  2. 字符串化和连接发生
  3. 所有定义的宏都被扩展,包括在字符串化

无间接的分步示例

main.c

#define CAT(x) pref_ ## x
#define Y a

CAT(Y)

中生成的宏,并将其扩展为:

gcc -E main.c

we get:

pref_Y

因为:

第 1 步:YCAT 的宏参数。

x 出现在字符串化 pref_ ## x 中。因此,Y 按原样粘贴,不进行扩展,给出:

pref_ ## Y

步骤 2:发生串联,我们剩下:

pref_Y

步骤 3:发生任何进一步的宏替换。但 pref_Y 不是任何已知的宏,因此将其保留。

我们可以通过实际向 pref_Y 添加定义来证实这一理论:

#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf

CAT(Y)

现在的结果是:

asdf

因为在上面的第 3 步中,pref_Y 现在被定义为宏,因此展开。

间接的分步示例

如果我们使用两步模式:

#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a

CAT(Y)

我们会得到:

pref_a

步骤 1:评估CAT

CAT(x) 被定义为 CAT2(x),因此定义中 CAT 的参数 x 并不出现在字符串化中:字符串化仅在 CAT2 扩展后发生,这在本步骤中看不到。

因此,Y 在被替换之前已完全扩展,经历步骤 1、2 和 3,我们在这里省略这些步骤,因为它很容易扩展为 a。因此,我们将 a 放入 CAT2(x) 中,给出:

CAT2(a)

步骤 2:无需进行字符串化

步骤 3:展开所有现有宏。我们有宏 CAT2(a),因此我们继续扩展它。

步骤3.1:CAT2的参数x出现在字符串化pref_## x中。因此,按原样粘贴输入字符串 a,给出:

pref_ ## a

步骤 3.2:字符串化:

pref_a

步骤 3:展开任何其他宏。 pref_a 不是任何宏,所以我们完成了。

GCC 参数预扫描文档

GCC 关于此事的文档也值得一读:https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

奖励:这些规则如何防止嵌套调用变得无限

现在考虑:

#define f(x) (x + 1)

f(f(a))

这扩展了to:

((a + 1) + 1)

而不是无限。

让我们分解一下:

第 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 的定义中,给出:

((a + 1) + 1)

第 2 步和第 3 步:没有发生太多事情,因为我们没有字符串化,也没有更多的宏可以扩展。

Plain-English explanation of the EVALUATOR two-step pattern

I 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:

  1. (prescan) Macro arguments are replaced:
    • if they are part of concatenation (A ## B) or stringification (#A), they are replaced exactly as the string given on the macro call, without being expanded
    • otherwise, they are first fully expanded, and only then replaced
  2. Stringification and concatenation happen
  3. All defined macros are expanded, including macros generated in stringification

Step-by-step example without indirection

main.c

#define CAT(x) pref_ ## x
#define Y a

CAT(Y)

and expand it with:

gcc -E main.c

we get:

pref_Y

because:

Step 1: Y is a the macro argument of CAT.

x appears in a stringification pref_ ## x. Therefore, Y gets pasted as is without expansion giving:

pref_ ## Y

Step 2: concatenation happens and we are left with:

pref_Y

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:

#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf

CAT(Y)

and now the result would be:

asdf

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:

#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a

CAT(Y)

we get:

pref_a

Step 1: CAT is evaluated.

CAT(x) is defined as CAT2(x), so argument x of CAT at the definition does not appear in a stringification: the stringification only happens after CAT2 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 to a. So we put a in CAT2(x) giving:

CAT2(a)

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 of CAT2 appears in a stringification pref_ ## x. Therefore, paste the input string a as is, giving:

pref_ ## a

Step 3.2: stringify:

pref_a

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:

#define f(x) (x + 1)

f(f(a))

which expands to:

((a + 1) + 1)

instead of going infinite.

Let's break it down:

Step 1: the outer f is called with argument x = f(a).

In the definition of f, the argument x is not part of a concatenation in the definition (x + 1) of f. 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, giving x = (a + 1).

Now back in Step 1, we take that fully expanded x argument equaling (a + 1), and put it inside the definition of f giving:

((a + 1) + 1)

Steps 2 and 3: not much happens, because we have no stringification and no more macros to expand.

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