预处理器宏扩展为另一个预处理器指令

Posted

技术标签:

【中文标题】预处理器宏扩展为另一个预处理器指令【英文标题】:Preprocessor macro expansion to another preprocessor directive 【发布时间】:2009-08-11 18:12:36 【问题描述】:

最初我认为我需要这个,但我最终避免了它。然而,我的好奇心(以及对知识的渴望,哼)让我问:

可以预处理宏,例如

#include "MyClass.h"

INSTANTIATE_FOO_TEMPLATE_CLASS(MyClass)

扩展到另一个包含,例如 in

#include "MyClass.h"

#include "FooTemplate.h"
template class FooTemplate<MyClass>;

【问题讨论】:

有一天你会想要将你的代码转移到一个操作系统,比如 Linux,其中大小写很重要(即 Foo.h 和 foo.h 是两个不同的文件),然后是所有未被发现的拼写错误窗户将回家栖息。或者,如果你是一个 Linux 人,你总有一天会想要另辟蹊径,遇到不同但同样可怕的问题。 我同意文件名总是小写的规则。我这样写是因为我想排除小写转换的问题(类名->文件名)。但这值得一提,谢谢。 +1! 我们的规则是文件名与我们的类型和函数的命名方案完全匹配。我们和这里的提问者完全一样,类型名称 FooType 将在 FooType.h 中定义。与任何“风格”指南一样,选择一种风格并坚持下去。话虽如此,我们在 linux 上进行了大量开发,因此操作系统会自动为我们强制执行此规则....hmmmmm ..... 好的。那是一个错误。我不喜欢在文件名中使用大写字母,但我喜欢在类型名称中使用大写字母。但我应该只为这个问题写两个小写字母。 @Richard Corden:让文件系统和编译器强制执行规则可能是暂时的,任何到其他操作系统的端口都会完全消除这种安全性。至少,使用极权主义的小写文件名规则,在任何问题发生之前,危险的行为和错误就会立即显现出来。我更喜欢哪个。 @moala。再想一想,让我感到震惊的是,无论您做什么(小写或混合大小写匹配内容),都需要一些外部实用程序来强制执行。总有可能有人会犯错,除非您有操作系统支持(如我的情况),否则您必须通过一些脚本/工具检查这一点。检查所有小写字母更容易 - 但检查名称是否与文件中的至少一个标识符匹配并不难。 你甚至可能有一个静态分析工具来为你工作!!! ;) 盲塞 >. 【参考方案1】:

我认为不能做到,这是因为预处理器是single pass。所以它不能发出其他预处理器指令。

具体来说,来自 C99 标准(6.10.3.4 第 3 段):

3 结果完全 宏替换预处理令牌 序列不被处理为 预处理指令,即使它 像一个,...

有趣的是,这就是为什么将一元 _Pragma 运算符添加到 c99 中的原因。因为#pragma不能被宏发出,但是_Pragma可以。

【讨论】:

嗯,它当然可以发出指令。它不能做的是在同一个预处理器通道中处理它们。 由于### 字符在宏中具有特殊含义,我看不出您如何实际发出指令... 实际上 GCC 预处理器似乎允许“#define X #ifdef X”,其中 #ifdef 和第二个 X 之间的空格实际上是换行符,当您运行 cpp 时,这实际上会发出 #ifdef在上面。其他人可能想检查一下,因为我刚喝了几杯啤酒:-) 是的,似乎可以创建“类似”指令的东西,但标准明确指出(请参阅我的编辑)这样的构造不作为指令处理。 是的,我并不是说 OP 可以做他想做的事。【参考方案2】:

C 标准对预处理指令(C99 - 6.10(2) - 预处理指令)进行了说明:

预处理指令由一系列预处理标记组成,以 a # 预处理标记(在翻译阶段 4 开始时) ...

和(C99 - 6.10(7)):

预处理指令中的预处理标记不受宏的约束 除非另有说明,否则扩展。

示例:

#define EMPTY
EMPTY # include <file.h>

第二行的预处理标记序列不是预处理指令,因为它在翻译阶段 4 开始时不以 # 开头,即使在宏 EMPTY 被替换后它也会这样做

所以,不,宏不能扩展为“#include”预处理指令。这些指令需要在翻译阶段 4 开始时就位(当处理这些指令发生预处理时)。由于宏扩展发生在第 4 阶段,宏不能导致某些东西在第 4 阶段开始时存在。

但我想指出,以下确实有效:

#ifdef WIN32
#define PLATFORM_HEADER "platform/windows/platform.h"
#else
#define PLATFORM_HEADER "platform/linux/platform.h"

#include PLATFORM_HEADER

因为 C 标准这么说(C99, 6.10.2(4) - 源文件包含):

表单的预处理指令

# include pp-tokens new-line

(与前两种形式之一不匹配)是允许的。预处理 指令中包含后的标记与普通文本一样处理。 (每个 当前定义为宏名称的标识符被其替换列表替换 预处理令牌。)

【讨论】:

我查看了标准的那部分,但我认为它不是最相关的部分。该示例不是尝试发出指令的示例。相反,它是一个前面没有“空白”的指令示例(即使 EMPTY 宏解析为空白)。 我同意您对标准的引用更直接相关,但是当我回答时它不存在。即使上面的示例与所询问的不同,关于需要在第 4 阶段开始时就位的预处理指令的位也不允许宏解释为有用的预处理指令,即使 6.10.3.4(3)说得更直接。 (换句话说,即使我不认为我的答案不正确,你的答案显然更好)。 抱歉,我必须选择一个好的答案。我希望您能理解,对您有见地的评论再 +1 以尽可能公平。 无需道歉——埃文的回答肯定更好。即使不是这样,也没有必要道歉。【参考方案3】:

所有预处理器指令在宏扩展开始之前被解释,所以不,你不能将宏扩展为 #include 指令并让它被解释为这样。相反,它将被解释为(错误的)C++ 代码。

【讨论】:

您可能会得到这种印象,因为许多指令禁用其参数的宏扩展,但事实并非如此。宏扩展与指令解析同时发生。如果它是真的,#if 将不起作用,并且 #undef 不会产生人们期望的效果。

以上是关于预处理器宏扩展为另一个预处理器指令的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 预处理器宏中处理数组元素?

使用预处理器宏组成另一个宏调用

使用预处理器宏进行条件编译 Xcode

用于返回重复一定次数的字符串的 C 预处理器宏

可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串

预处理器宏更改两个目标