你可以嵌套 C 预处理器指令吗?

Posted

技术标签:

【中文标题】你可以嵌套 C 预处理器指令吗?【英文标题】:Can you nest C preprocessor directives? 【发布时间】:2009-01-09 06:14:21 【问题描述】:

例如,以下是否可能:

#define definer(x) #define #x?

【问题讨论】:

出于好奇,您为什么需要它? 一种用途是让 CPP 在不同的上下文中对语句进行两次评估。我有一个系统,它使用 grep/sed 通过这样做来扩展向量方程。 【参考方案1】:

不,你不能那样做。 磅 (#) 符号在定义中具有不同的含义。这意味着 - 如果这是一个参数,则通过引用它使其成为一个字符串。

【讨论】:

技术术语是字符串化操作符(而## 是标记粘贴操作符)。 Err.. 你最近见过 assert() 吗?最简单的形式:#define assert(expr) if (!(expr)) printf("Assertion failed (%s) at file '%s', line %d.\n", #expr, FILE , LINE); abort(); 所以,真的,你可以,但是它滥用了预处理器。如果您注意如何称呼它...没关系 另外,评论一下assert的脑残,如果不能分配10个字节,assert()也必然会失败。糟糕的现实世界例子,但说明了我的观点。 我看不出 assert 与这个答案或问题有什么关系 那个断言宏实际上是使用#字符串化操作符。 #expr 为 assert(expr) 创建参数的 C 字符串。【参考方案2】:

您不能嵌套 C 预处理器指令。幸运的是,它几乎从来没有必要。 如果你真的需要那种力量,你几乎肯定会更好 在将代码交给 C 编译器之前运行的预处理器。例如:

sed 's/@CUSTOMER@/J. Random Person/' foo.c.in > foo.c
cc foo.c

另一个有用的技巧是将技巧隔离到一个单独的头文件中,即 由您自己编写的程序生成:

./generate-trickery --greet 'J. Random Person' > foo.h

foo.h 看起来像这样:

#define GREET(x) ("J. Random Person greets you, " #x)

如果您将它与 Makefile 或其他自动化结合在一起,它将是 非常无缝,不会妨碍您的开发。

【讨论】:

【参考方案3】:

不,你不能那样做。

您可以从另一个宏中引用一个宏,但不能从另一个宏中定义一个宏。

【讨论】:

【参考方案4】:

如果您尝试创建一段可以多次调用以执行略有不同的事情的预处理器代码,您可以执行此操作的一种(适度糟糕的)方法是将代码隔离到单个 .h 文件中然后#include 几次。这个想法是,每次您#include 文件时,您都在“调用您的例程”——您首先通过 #defineing 包含文件所引用的某些预处理器常量来“传递参数”。

我发现这很有用的一个地方是生成“智能”枚举,这些枚举可以转换为/从它们的“字符串化”形式转换(这对 I/O 很有用)。您创建一个 .h 文件,其中包含例如

ENUMVAL(foo)
ENUMVAL(bar)
ENUMVAL(baz)

然后是#include这个文件两次:一次是ENUMVAL()被定义为创建enum声明,一次是ENUMVAL()被定义为生成一个数组字符串化的名称。通过这种方式,您无需多次指定实际令牌列表。

【讨论】:

这种技术叫做 X-Macro 啊!我不知道它有名字。谢谢! :) @qrdl 和 j_random_hacker:X-Macro +1。检查了 Dr.Dobbs 上的this article,发现它对于涉及代码的不同部分以及具有相似名称的参数列表的某些情况很有用。还有this article 解释更多但显示的代码更少。【参考方案5】:
#define definer(x) #define #x?

#x 是 x 的字符串化。您不能#define 字符串标记。 (#define "foo"。)它必须是一个标识符 [a-zA-Z0-9_]* 标记。

不能像这样嵌套#define。 #define 中不能有#define。

可以在#if 块中包含#if。

#ifdef FOO

#ifdef BAR
 ...
#else // BAR
 ...
#endif // BAR

#else // FOO
 ...
#endif //FOO

您在#if 宏中可以使用的表达式也受到一定限制。但有时你可以解决这个问题。例如:

        /* Use CONCATENATE_4_AGAIN to expand the arguments to CONCATENATE_4 */
#define CONCATENATE_4(      a,b,c,d)  CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d)  a ## b ## c ## d

    /* Creates a typedef that's legal/illegal depending on EXPRESSION.       *
     * Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*".              *
     * (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT)                     \
  typedef char CONCATENATE_4( static_assert____,      IDENTIFIER_TEXT,  \
                              ____failed_at_line____, __LINE__ )        \
            [ (EXPRESSION) ? 1 : -1 ]

加上类似的东西:

STATIC_ASSERT( sizeof(int1) == 1, sizeof_int1_equal_1 );

(是的,我知道#include 。这只是一个例子。)

【讨论】:

【参考方案6】:

虽然您的语法无效,但您的问题的答案在技术上是肯定的。但这只能通过使您的代码不可读和不可维护的令人讨厌的技巧来实现。

另请参阅:http://www.ioccc.org/years.html#1995_vanschnitz 和 http://www.ioccc.org/years.html#2004_vik2

【讨论】:

我在链接中看不到 Yes.. 只有奇怪的混淆 C 代码,你能举个例子吗?

以上是关于你可以嵌套 C 预处理器指令吗?的主要内容,如果未能解决你的问题,请参考以下文章

Go中的预处理器指令就像C中的一样[重复]

预处理器指令#include 可以禁用/排除吗?

#define 预处理器指令可以包含 if 和 else 吗?

可以在 #ifndef ... #endif 等预处理器指令块中的 VS2008 中启用智能感知吗

C预处理器和C库--1

预处理器指令反射注入