3

免责声明:我已经看过类似于Stringification of a macro value 的问题的答案。

考虑以下测试程序:

#include <stdio.h>
#define QUOTE(str) #str
#define EXPAND_AND_QUOTE(str) QUOTE(str)

#define MACRO HelloWorld

int main() {
    printf("%s\n", EXPAND_AND_QUOTE(MACRO));
    printf("%s\n", QUOTE(MACRO));

    #undef MACRO
    printf("%s\n", EXPAND_AND_QUOTE(MACRO));
    printf("%s\n", QUOTE(MACRO));
}

这个程序的输出是:

HelloWorld
MACRO
MACRO
MACRO

所需的输出是

HelloWorld
MACRO

MACRO

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

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

printf("%s\n", QUOTE());

也就是说,我希望将未定义的宏扩展为“虚无”,然后将该“虚无”传递给引用函数。

4

5 回答 5

3

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

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

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

#undef MACRO
printf("%s\n", EXPAND_AND_QUOTE(MACRO));

#define FOO MACRO
printf("%s\n", EXPAND_AND_QUOTE(FOO));

一旦FOO在后者中被替换,我们有两种情况:一种,我们有,MACRO因为它没有改变。另一方面,我们有,MACRO因为它被替换了FOO。在那之后,行为必须相同。但是问题要求需要不同的行为,""前者和"MACRO"后者。由于相同的行为不能产生不同的结果,因此这是行不通的。(但更多关于这一点的下文。)

第二种可能性,用另一个值替换,不会发生,因为MACRO没有定义。

第三种可能性只是产生一些新的记号,例如FOOMACRO. 然而,虽然我们可以选择第一部分,但我们不能选择后部分,所以我们无法知道会产生什么令牌,并且我们可以用它来做任何事情,这会根据是否MACRO定义而有所不同。

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

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

于 2013-10-25T23:44:29.220 回答
2

尝试这个:

#undef MACRO
#define MACRO

或者:

#undef MACRO
#ifndef MACRO
#  undef EXPAND_AND_QUOTE
#  define EXPAND_AND_QUOTE(str) ""
#endif
于 2013-10-25T23:18:48.243 回答
1

而不是#undef MACRO尝试:

#undef MACRO
#define MACRO \0

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

于 2013-10-25T23:25:44.243 回答
1

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

#define MACRO

在#undef 之后——也就是说,定义MACRO为一个扩展为空的宏。

于 2013-10-26T00:24:47.423 回答
-1

可以做到,但有两个警告:

  • 您的编译器必须支持可变参数宏(它们是 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("%s\n", EXPAND_AND_QUOTE(MACRO));
    printf("%s\n", QUOTE(MACRO));

#undef MACRO
    printf("%s\n", EXPAND_AND_QUOTE(MACRO));
    printf("%s\n", 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

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

于 2016-07-07T20:59:19.827 回答