我可以在字符串化之前强制未定义的宏扩展吗?
Can I force an undefined macro to expand before stringification?
免责声明:我已经查看了类似于宏值字符串化的问题的答案。
请考虑以下测试程序:
#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"
,不能利用它。
我唯一模糊的希望是产生两个令牌,一个是让令牌被替换的结果,另一个不是。 例如,我们可以产生这样一种情况,在这种情况下,鉴于上述MACRO
和FOO
定义的非定义,EXPAND_AND_QUOTE(MACRO)
在辅助宏的扩展链中的某个地方产生两个标记SomethingMACRO
和SomethingMACRO
,而EXPAND_AND_QUOTE(FOO)
产生SomethingFOO
和SomethingMACRO
。这是可能的,但是我们如何处理这两个令牌?我们可以将两者串起来,然后在运行时将它们与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
中的第二个值从命名参数列表的末尾推开(并进入省略号),然后返回。
- 编译C++时未定义的引用
- vscode g++链路故障:体系结构x86_64的未定义符号
- 如何修复此错误:未定义对"距离(浮点数,浮点数,浮点数,浮点数,浮点数)"的引用
- 我的项目不会像"undefined reference to `grpc::g_core_codegen_interface'"那样使用未定义的引用错误进行编译
- 不知道某个东西是否被忽略会引入未定义的行为吗
- 对C宏的未定义引用,但在定义它时会出现重新定义错误
- 未定义的引用在哪里
- 编译时的 CImg 库返回对"__imp_SetDIBitsToDevice"的未定义引用
- 对Py_Initialize()的未定义引用
- c++11评估顺序(未定义的行为)
- 使用mysql c++连接器的未定义引用
- 扩展string_view是未定义的行为吗
- 尝试使用 Cython 扩展时未定义的符号
- 预处理器,在未定义时将宏扩展为无
- 构建php扩展时未定义的引用
- 尝试扩展 ctype 分面时出现未定义的引用错误
- 我可以在字符串化之前强制未定义的宏扩展吗?
- 加载使用 SWIG 制作的 php 扩展时未定义的符号
- Python C 扩展使用 numpy 和 gdal 在运行时给出未定义的符号
- qml扩展上的未定义符号(来自示例)