能否在宏内定义宏?

6
我希望能够像这样使用宏参数:

  #define D(cond,...) do{         \
    #if cond                      \
    #define YYY 1                 \
    #else                         \
    #define YYY 0                 \
  } while(0)

这是可能的吗?

更新
也许当源码被预处理两次时:gcc -E source.c | gcc -xc - 下面的操作将会生效:

#define D(cond,...) #define YYY cond&DEBUG
#if YYY
#define D(...) printf( __VA_ARGS__ )
#else
#define D(...)
#endif

2
不。*<填充>* - Eugene Sh.
1
你为什么认为你需要这样做? - StoryTeller - Unslander Monica
5
我指的是字面上的“不”。请停止滥用宏,因为这被认为是一种不良实践。 - Eugene Sh.
4
想一想:预处理器不可能知道运行时会发生什么,因此在预处理器完成后定义宏是不可能的。 - ack
1
@EugeneSh。万物皆有用。 - machine_1
显示剩余6条评论
6个回答

6
这是不可能的。阅读GNU cpp预处理器和C11标准(即n1570),并在这里检查。C预处理器(至少在概念上)在编译器的其余部分之前运行(获得您的翻译单元预处理形式)。顺便说一句,对于一个名为foo.c的文件,你可以使用gcc -C -E foo.c > foo.i(使用GCC)来进入foo.i它的预处理形式,并且你可以使用分页器或编辑器检查那个foo.i,因为它是一个文本文件。

然而,可以生成一个.c文件(自从20世纪80年代以来,生成C代码是一种常见的做法;例如使用yaccbisonrpcgenswig等专门生成C或C++代码的生成器;许多大型软件项目使用专门的C或C++代码生成器...)。您可以考虑使用其他工具,例如GPP预处理器(或GNU m4)或其他程序或脚本,从其他东西中生成您的C文件。还要查看autoconf(它可能有与您相似的目标)。

您可能需要配置您的构建自动化工具,例如编辑Makefile文件用于GNU make


6
不行,因为C 2011 [N1570] 6.10.3.4 3中提到,关于宏替换,“即使类似预处理指令,结果完全替换的预处理标记序列也不会被视为预处理指令进行处理…”

4
不,这是不可能的。
在翻译过程中,所有预处理指令(例如 #define、#include 等)都会在任何宏扩展之前执行,因此如果一个宏扩展成一个预处理指令,它将不会被解释为预处理指令 - 而会被解释为(无效的)源代码。

如果在任何宏展开之前执行了所有预处理指令,#if MyPreprocessorSymbol == 3 将无法工作。您必须在评估 #if 之前展开预处理符号。您甚至可以在 #include 语句中使用预处理宏,根据 C 2011 [N1570] 6.10.2 4。 - Eric Postpischil

3
正如其他人指出的那样,这是不可能的,但有一个解决方法:
int YYY;
/* global scope variables are sometimes considered bad practice... */
#define D(cond,...) do{         \
  if (cond) {                   \
  YYY = 1;                      \ 
  }                             \
  else {                        \
  YYY = 0;                      \
  }                             \
} while(0)

使用优化标志(例如:gcc/clang -O3),它将像宏一样替换死代码。显然,您可能希望更改YYY的类型,但是您似乎像布尔变量一样使用它。


你好。我尝试了您的示例,但它总是生成 if(0){ ...,尽管第一个参数为 D(1, "some string" )。此外,我发现一个有趣的技巧:我们可以对源码进行两次预处理:gcc -E source.c | gcc -xc - - Eugen Konkov
这是一个真正很棒的技巧!我编辑了我的答案,非常抱歉我没有理解你想要使用cond作为字面量,我以为那是一个定义条件...现在这应该可以工作了。 - ft_error
是的,它应该是定义条件。当 cond 为0时,不应生成任何代码。 - Eugen Konkov

2
不可以。C预处理器无法知道运行时会发生什么。预处理器在编译之前就遍历整个程序,将每个定义的宏替换为其分配的值。

这不涉及翻译后的预处理。问题中的想法是D宏将在源代码的某个地方被扩展,然后,在该扩展之后,替换文本将被重新扫描以查找预处理器指令。这将在预处理期间全部发生。 - Eric Postpischil
@EricPostpischil:有趣的技巧是对源代码进行两次预处理:gcc -E source.c | gcc -xc - - Eugen Konkov
@EugenKonkov:在问题的第一个代码序列上,这并不起作用,因为在预处理之前的翻译阶段2中,\后面跟着的换行符被删除了,因此生成的D宏在其替换文本中没有换行符,因此扩展的预处理指令不是单独的行。它在第一个或第二个代码序列上都不起作用,因为#是宏替换中的运算符(它将字符串化),必须跟随宏参数,因此#if是非法的。 - Eric Postpischil

1
这是一种简单的代码生成方式,用于在整合其他工具到项目中过于复杂时使用。
定义一个宏,根据您的需要进行扩展:
#define NESTED              /* Comment out instead of backslash new lines.
*/                          /*
*/  UNDEF   REPLACED        /*
*/                          /*
*/  IFDEF   CONDITION       /*
*/  DEFINE  REPLACED  1     /*
*/  ELSE                    /*
*/  DEFINE  REPLACED  0     /*
*/  ENDIF

你的 NESTED 版本可以是类似函数宏的形式,而 REPLACED 可以有更详细的主体。
不要定义 CONDITION 和指令命名的宏。
定义 CONDITION 来控制编译时 NESTED 获取哪个值,类似于普通的 #ifdef 使用:
DEFINE  CONDITION
NESTED
int i = REPLACED;        //i == 1

UNDEF  CONDITION
NESTED
int z = REPLACED;        //z == 0

使用NESTED和其他宏的源代码将无法编译。要生成一个.c.cpp文件,使其能够与您选择的选项一起编译,请执行以下操作:
gcc   -E -CC   source.c  -o temporary.c
gcc   -E                                                  \
   -DDEFINE=\#define  -DUNDEF=\#undef                     \
   -DIFDEF=\#ifdef    -DELSE=\#else    -DENDIF=\#endif    \
   temporary.c  -o usableFile.c

rm temporary.c     #remove the temporary file

-E 表示只进行预处理,不进行编译。第一个 gcc 命令会将 NESTED 和所有通常定义的宏从源代码中展开。由于未定义 DEFINEIFDEF 等,它们及其后续参数在 temporary.c 文件中保留为文字。

-CC 使注释保留在输出文件中。在预处理器将 NESTED 替换为其正文后,temporary.c 包含单独行中的指令宏及其注释。当注释在下一个 gcc 命令中被移除时,行结束符仍然按标准保留。

# 可以出现在不带参数的宏体中。然而,与宏不同,指令不会在展开时重新扫描和执行,因此需要另一次预处理才能使嵌套定义起作用。所有与延迟定义相关的预处理都需要延迟,并一次性提供给预处理器。否则,后续步骤中需要的指令和参数会在前面的步骤中被消耗并从代码中删除。

第二个gcc命令通过延迟指令替换-D宏,使得它们在下一次预处理中可用。这些指令及其参数不会在同一个gcc命令中被重新扫描,而是保留为文字形式在usableFile.c中。
当您编译usableFile.c文件时,预处理器会执行延迟的指令。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接