我可以在字符串化之前强制未定义的宏扩展吗?

Can I force an undefined macro to expand before stringification?

本文关键字:未定义 扩展 字符串 我可以      更新时间:2023-10-16

免责声明:我已经查看了类似于宏值字符串化的问题的答案。

请考虑以下测试程序:

#include <stdio.h>
#define QUOTE(str) #str
#define EXPAND_AND_QUOTE(str) QUOTE(str)
#define MACRO HelloWorld
int main() {
    printf("%sn", EXPAND_AND_QUOTE(MACRO));
    printf("%sn", QUOTE(MACRO));
    #undef MACRO
    printf("%sn", EXPAND_AND_QUOTE(MACRO));
    printf("%sn", QUOTE(MACRO));
}

该程序的输出是:

HelloWorld
MACRO
MACRO
MACRO

所需的输出为

HelloWorld
MACRO
MACRO

有没有办法重新定义我的宏(即不是宏的元宏)以获得所需的输出?

特别是,我希望第三printf应该等同于:

printf("%sn", QUOTE());

也就是说,我希望将未定义的宏扩展到"虚无",然后将该"虚无"传递给报价函数。

这在标准 C 中是不可能的。

在宏替换期间,令牌可能会发生四件事。它可以保持不变,可以用替换值替换,可以与另一个标记连接,并且可以字符串化。

如果MACRO不变,则无法区分这两种情况:

#undef MACRO
printf("%sn", EXPAND_AND_QUOTE(MACRO));
#define FOO MACRO
printf("%sn", EXPAND_AND_QUOTE(FOO));

一旦FOO在后者中被替换,我们就有两种情况:一种情况是,我们有MACRO,因为它没有变化。另一方面,我们有MACRO,因为它被替换为FOO .在此之后,行为必须相同。但是问题要求要求不同的行为,""前者,"MACRO"后者。由于相同的行为不会产生不同的结果,因此这将不起作用。(但更多关于这一点的信息见下文。

第二种可能性,用

另一个值替换,不会发生,因为MACRO没有定义。

第三种可能性只是产生一些新的令牌,例如FOOMACRO。但是,尽管我们可以选择第一部分,但我们不能选择后一部分,因此我们无法知道会产生什么令牌,并且我们可能不会对它做什么,这取决于是否定义了MACRO

第四种可能性与第三种有同样的问题,我们生产"MACRO",不能利用它。

我唯一模糊的希望是产生两个令牌,一个是让令牌被替换的结果,另一个不是。 例如,我们可以产生这样一种情况,在这种情况下,鉴于上述MACROFOO定义的非定义,EXPAND_AND_QUOTE(MACRO)在辅助宏的扩展链中的某个地方产生两个标记SomethingMACROSomethingMACRO,而EXPAND_AND_QUOTE(FOO)产生SomethingFOOSomethingMACRO。这是可能的,但是我们如何处理这两个令牌?我们可以将两者串起来,然后在运行时将它们与strcmp进行比较。但是,我认为在编译时无法做任何事情。此外,它无法区分FOO被定义为#define FOO FOO的情况;如果我们成功地比较上述代币,这将产生"",但要求它产生"FOO"

试试这个:

#undef MACRO
#define MACRO

或者:

#undef MACRO
#ifndef MACRO
#  undef EXPAND_AND_QUOTE
#  define EXPAND_AND_QUOTE(str) ""
#endif

与其尝试#undef MACRO

#undef MACRO
#define MACRO 

\0 确保 MACRO 可以传递(因为某些编译器 VC++ 咳嗽无法解析宏没有它),但仍然是一个空白字符串。 \0 是字符串的 ASCII 结尾的转义字符,实质上是将 MACRO 设置为空白字符串。

标题中的问题实际上毫无意义——一个未定义的宏如何扩展? 它未定义,因此没有什么可扩展的。 获得您想要的输出的解决方案是添加

#define MACRO

紧跟在 #undef 之后 - 也就是说,将MACRO定义为扩展到无的宏。

这可以完成,但有两个警告:

  • 编译器必须支持可变参数宏(它们是 C99 标准的一部分)
  • 定义
  • 宏令牌时,您必须添加一个0,,即

    #define MACRO 0, HelloWorld
    

鉴于上述内容,您可以使用我想出的这个漂亮的RESOLVE宏。这是一个单行版本(它可以做得更防弹/便携):

#define RESOLVE(token, default_token, ...) default_token

在这里,它应用于您的示例代码:

    #include <stdio.h>
#define BLANK
#define RESOLVE(token, default_token, ...) default_token
#define QUOTE(str) #str
#define QUOTE0(str) QUOTE(str) // need the intermediate QUOTE0 function here or else you get output like `RESOLVE(0, HelloWorld, , )` instead of `HelloWorld`
#define EXPAND_AND_QUOTE(str, ...) QUOTE0(RESOLVE(str, BLANK, __VA_ARGS__)) // the BLANK token is optional here, can also be a literal blank
#define MACRO 0, HelloWorld
int main() {
    printf("%sn", EXPAND_AND_QUOTE(MACRO));
    printf("%sn", QUOTE(MACRO));
#undef MACRO
    printf("%sn", EXPAND_AND_QUOTE(MACRO));
    printf("%sn", QUOTE(MACRO));
}

当我编译和测试它时,输出确实是

HelloWorld
MACRO
MACRO

RESOLVE的工作原理

基本思想是,虽然预处理器无法区分已定义和未定义的标记(因为它不区分未定义的标记和常规文本),但它可以区分单个标记和元组,至少在类似函数的宏的参数列表中。 __VA_ARGS__ 然后允许宏在此区别的基础上产生不同的行为。

假设您有两个定义的令牌

#define BAR bar
#define BAZ baz0, baz1

以及一个未定义的令牌FOO .当您将这些传递给RESOLVE 时,它们有效地扩展到

RESOLVE(FOO, BLANK) -> RESOLVE(token=, default_token=BLANK, __VA_ARGS__=)         -> BLANK
RESOLVE(BAR, BLANK) -> RESOLVE(token=bar, default_token=BLANK, __VA_ARGS__=)      -> BLANK
RESOLVE(BAZ, BLANK) -> RESOLVE(token=baz0, default_token=baz1, __VA_ARGS__=BLANK) -> baz1

BAZ的情况下,BLANK被存储在BAZ中的第二个值从命名参数列表的末尾推开(并进入省略号),然后返回。