是否可以在宏内部定义宏?

Posted

技术标签:

【中文标题】是否可以在宏内部定义宏?【英文标题】:Is it possible to define macro inside macro? 【发布时间】:2018-01-24 20:47:26 【问题描述】:

我想像这样使用宏参数:

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

有可能吗?

UPD 也许当源将被预处理两次时:gcc -E source.c | gcc -xc - next 将起作用:

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

【问题讨论】:

没有。 你认为你为什么需要这样做? 我的意思是“不”。并停止滥用宏,这被认为是一种不好的做法。 想一想:预处理器不可能知道运行时发生了什么,所以不可能在预处理器完成后定义宏。 @EugeneSh。万物皆有其用。 【参考方案1】:

这是不可能的。阅读GNU cpp preprocessor和C11标准(即n1570),并检查here。 C preprocessor (至少在概念上)在编译器的其余部分之前运行(它会获得translation unit 的预处理 形式)。顺便说一句,对于文件foo.c,您可以使用gcc -C -E foo.c > foo.i(使用GCC)进入foo.i 的预处理形式,您可以检查foo.i - 因为它是一个文本文件 - 带有寻呼机或编辑。

但是,可以生成.c 文件(生成 C 代码是一种常见做法,至少自 1980 年代以来是这样;例如使用 yacc、bison、rpcgen、 swig, ....;许多大型软件项目使用专门的 C 或 C++ 代码生成器...)。您可能会考虑使用其他工具,可能是GPP 预处理器(或GNU m4)或其他程序或脚本,以生成您的C 文件(从其他东西)。还请查看autoconf(它可能有与您相似的目标)。

您可能需要为此目的配置您的 build automation 工具,例如编辑您的Makefile 为GNU make

【讨论】:

【参考方案2】:

不,因为C 2011 [N1570] 6.10.3.4 3 说,关于宏替换,“生成的完全被宏替换的预处理标记序列即使类似于一个预处理指令也不会被处理,......”

【讨论】:

【参考方案3】:

不,这是不可能的。

在翻译过程中,所有预处理指令(#define#include 等)都会在任何宏扩展发生之前执行,因此如果宏扩展为预处理指令,它不会被解释为 - 它将被解释为(无效的)源代码。

【讨论】:

如果在任何宏扩展之前执行了所有预处理指令,#if MyPreprocessorSymbol == 3 将不起作用。在评估#if 之前,您必须扩展预处理器符号。根据 C 2011 [N1570] 6.10.2 4,您甚至可以在 #include 语句中使用预处理器宏。【参考方案4】:

不,你不能。 C 预处理器无法知道运行时会发生什么。

预处理器在程序编译之前就对其进行检查,并将每个定义的宏替换为其分配的值。

【讨论】:

这里不涉及翻译后的预处理。问题中的想法是 D 宏将在源代码稍后的某个位置扩展,并且在扩展之后,替换文本将被重新扫描以查找预处理器指令。这都将在预处理期间发生。 @EricPostpischil:有趣的技巧是对源进行两次预处理:gcc -E source.c | gcc -xc - @EugenKonkov:这不适用于问题中的第一个代码序列,因为` followed by new-line is deleted during translation phase 2, before preprocessing, so the resulting D` 宏在其替换文本中没有换行符,因此未启用扩展的预处理指令单独的行。它不适用于第一个或第二个代码序列,因为#是宏替换中的运算符(它是字符串化的)并且必须后跟宏参数,因此#if是非法的。【参考方案5】:

正如其他人指出的那样,这是不可能的,但有一种解决方法:

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) ...,尽管第一个参数是DD(1, "some string" )。我还发现了一个有趣的技巧:我们可以对源进行两次预处理:gcc -E source.c | gcc -xc - 这是一个非常好的技巧!我编辑了我的答案,很抱歉我不明白你想使用 cond 作为文字,我认为这是一个定义条件......现在这应该可以了。 是的,应该是定义条件。当cond 为 0 时,不应生成任何代码。【参考方案6】:

这是一些穷人的代码生成,因为将另一个工具集成到项目中是多余的。

定义一个这样的宏,根据您的需要进行扩展:

#define NESTED              /* Comment out instead of backslash new lines.
*/                          /*
*/  UNDEF   REPLACED        /*
*/                          /*
*/  IFDEF   CONDITION       /*
*/  DEFINE  REPLACED  1     /*
*/  ELSE                    /*
*/  DEFINE  REPLACED  0     /*
*/  ENDIF

您的NESTED 版本可以是类似函数的宏,而REPLACED 可以有更精细的主体。

CONDITION 和名为宏的指令不带定义。

DEFINE 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 使 cmets 保留在输出文件中。在预处理器用它的主体替换NESTED 之后,temporary.c 在单独的行中包含指令宏以及 cmets。当在下一个 gcc 命令中删除 cmets 时,换行符仍按标准保留。

# 在不带参数的宏的主体中被接受。但是,与宏不同,指令不会在扩展时重新扫描和执行,因此您需要另一个预处理器传递来使嵌套定义工作。所有与延迟定义相关的预处理也需要延迟,并立即提供给预处理器。否则,稍后传递所需的指令和参数将被消耗并从前一个传递中的代码中删除。

第二个gcc 命令将-D 宏替换为延迟指令,使它们在下一次传递时对预处理器可用。指令及其参数不会在同一个 gcc 命令中重新扫描,并在 usableFile.c 中保留为文字文本。

当你编译 usableFile.c 时,预处理器会执行延迟的指令。

【讨论】:

以上是关于是否可以在宏内部定义宏?的主要内容,如果未能解决你的问题,请参考以下文章

在宏内部使用变量内部数据集名称时,SAS语法错误22和200

C 语言宏定义中使用do...while

在宏内取消字符串化

有没有办法在 Android.bp 文件中定义 C 语言宏?

宏定义与内联函数

在宏参数中使用 #line 指令是不是合法?