【问题标题】:How to defer a macro substitution in a concatenate-stringify cascade如何在串联字符串化级联中推迟宏替换
【发布时间】:2018-05-30 01:56:09
【问题描述】:

这是this one 的后续问题(也更接近手头的实际问题)。

如果我有以下情况:

#include <stdio.h>

#define FOO_ONE 12
#define FOO_TWO 34
#define BAR_ONE 56
#define BAR_TWO 78

#define FOO 99

#define STRINGIFY(mac) #mac
#define CONCAT(mac1, mac2) STRINGIFY(mac1) STRINGIFY(mac2)
#define MAKE_MAC(mac) CONCAT(mac##_ONE, mac##_TWO)

#define PRINT(mac) printf(#mac ": " MAKE_MAC(mac) "\n")

void main(int argc, char *argv[])
{
    PRINT(FOO);
    PRINT(BAR);
}

可以看出,字符串化的连接宏随后被替换在 printf() 语句中,该语句本身位于宏中。

因为FOO被定义(如99),它恰好在与_ONE_TWO连接之前被扩展,有效地创建了标记99_ONE99_TWO

这个程序输出:

FOO: 99_ONE99_TWO
BAR: 5678

如何推迟 FOO 宏的扩展(实际上,完全消除它,以获得所需的输出:

FOO: 1234
BAR: 5678

注意:假设PRINT() 宏签名无法更改(即无法添加参数等)。然而,它的实现可以改变。此外,FOOFOO_*BAR_* 定义也不能修改。

【问题讨论】:

  • 由于FOOFOO_*BAR_*都不能被修改,如何防止PRINT(FOO)中发生从FOO99的替换?
  • @Stan - 确实是个问题......这就是为什么我正在寻找 SE 大师来寻找创意...... 8-)
  • @Stan - 我希望对底层宏级联进行重新排序可以解决问题,但您强调问题的根源在于 API 宏本身。
  • 确实,在这种情况下放弃宏观想法会更优雅。这种实现的最初需求是什么? FOO, BAR 不能是数组吗?正如您在另一条评论中提到的,有数百个宏,为了增加灵活性,我们还可以考虑将所有宏封装成“实际”变量和函数。

标签: c macros c-preprocessor evaluation


【解决方案1】:

如果您可以在每次使用FOO 时执行某项操作,您可以先取消定义宏,使其按您的需要扩展,然后重新定义,如下所示

#undef FOO
PRINT(FOO);
#define FOO 99

在这种情况下,它将扩展到

printf("FOO" ": " "12" "34" "\n");

printf("BAR" ": " "56" "78" "\n");

打印你想要的。

【讨论】:

  • 这可能是解决方案的开始。但是,您如何检测到何时使用FOO,而不是BAR?在实践中,FOOBAR 是一个包含 100 个宏的列表,我们从外部来源接收到这些宏。他们中的少数人有这个问题中表达的问题。
  • 也许如果,在PRINT() 的定义中,您可以将mac 值保存到临时宏中,然后取消定义mac,调用PRINT(),然后使用临时宏作为它的重新定义新价值?
  • @ysap 我正在尝试,但如果宏使用数百次,则无法找到解决方案。
  • @ysap 宏不是变量。你用#define 分配一个宏,你不能从替换列表中#define。您可以做的最接近“节省”之类的语义的是使用复杂技术的仅值操作,例如通过 boost pp 插槽完成的操作,其中涉及 #include(另一件您不能在宏内部做的事情)。跨度>
  • @HWalters - 当然,您是对的,这就是我在 SE 中发布此类问题的原因。很多时候你会得到一些关于如何利用 C 特性的新颖想法。
【解决方案2】:
如何推迟 FOO 宏的扩展...注意:假设 PRINT() 宏签名无法更改(即无法添加参数等)

你不能。

宏扩展经过一系列步骤:

  • 参数替换
  • 不按特定顺序粘贴和字符串化
  • 重新扫描并进一步更换

参数替换发生在您的参数中;在调用PRINT(FOO)FOO的情况下;这是第一步。当您甚至让预处理器识别出替换列表中的某些内容是宏时,您早已超越了参数替换。

参数替换的规则是,如果您的任何参数在替换列表中被提及,并且这些参数既没有被字符串化也没有被粘贴,那么相应的参数将被完全评估,并且这些参数的提及被替换为结果.在这种情况下,PRINT(FOO),在参数替换之后,会导致替换列表:

printf(#mac ": " MAKE_MAC(99) "\n")

同样,MAKE_MAC 的定义无关紧要;在重新扫描和进一步替换之前,它甚至不会被识别为宏。

现在,不受您的限制,您可以推迟 FOO 的扩展...通过将第二个参数添加到 PRINT 并粘贴到它(这将取消它从 a.s. 的资格,并且到时候重新扫描和替换来了沿着它会调用你的下一个宏)。但是有了你的限制,你就是 DOA。

【讨论】:

  • 感谢您的详细解释。基本上,@Stan 在对该问题的评论中所说的话。
【解决方案3】:

FOO 到 99 的实际扩展发生在 PRINT 扩展的那一刻,并且在它的主体再次被重新扫描之前。 ANSI标准的相关部分是:

6.10.3.1 参数替换

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

在您的情况下,您可以通过替换来避免扩展 FOO(在 mac 内)

#define PRINT(mac) printf(#mac ": " MAKE_MAC(mac) "\n")

#define PRINT(mac) printf(#mac ": " CONCAT(mac##_ONE, mac##_TWO) "\n")

【讨论】:

  • 感谢您的详细解释。基本上,@Stan 在对该问题的评论中所说的话。我选择这个答案而不是您提供标准报价的另一个 b/c。
  • @ysap 不确定是否理解您的评论。我已经回答了最初的问题:如何定义PRINT 以获得输出FOO: 1234 BAR: 5678
  • 玛丽安-当然,我没有阅读您答案的第二部分。巧合的是,在您发表评论之前,我也在考虑相同的思路,但您帮助确定了解决方案。我修改了程序,现在它构建得恰到好处!
猜你喜欢
  • 1970-01-01
  • 2018-11-13
  • 1970-01-01
  • 2023-04-01
  • 2018-02-05
  • 2021-11-29
  • 2021-01-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多