C/C++:预处理器指令应如何处理宏参数列表?

mrn*_*mrn 2 c c++ standards preprocessor c-preprocessor

C 和 C++ 标准都指定了以下内容:

\n
\n

16.3.1 参数替换 (C++11)

\n

6.10.3.1 参数替换 (C11)

\n

在识别出调用类似函数的宏的参数后,将进行参数替换。替换列表中的参数,除非前面有 # 或 ## 预处理标记或后面有 ## 预处理标记(见下文),否则在其中包含的所有宏都已展开后将被相应的参数替换。在被替换之前,每个参数\xe2\x80\x99s预处理标记被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理标记。

\n
\n

人们可以将本段解释为标准要求:

\n

(1) 首先识别宏参数(以逗号分隔),然后分别展开每个参数中包含的所有宏,

\n

或者

\n

(2) 展开参数列表中包含的所有宏,然后识别每个参数。

\n

为了说明这一点,让我们考虑以下示例代码:

\n
#define CONDITION (0)\n\n#if (CONDITION > 0)\n#define FunctionAlias(par_a, par_b, par_opt, par_c) \\\n          FunctionName(par_a, par_b, par_opt, par_c)\n#else\n#define FunctionAlias(par_a, par_b, par_c) \\\n          FunctionName(par_a, par_b, par_c)\n#endif\n\nint global_a, global_b, global_c;\n#if (CONDITION > 0)\nint global_opt;\n#endif\n\nvoid FunctionName(int a, int b, int c)\n{\n}\n \nvoid AnotherFunction()\n{\n   FunctionAlias(\n                  global_a,\n                  global_b,\n                  #if (CONDITION > 0)\n                  global_opt,\n                  #endif\n                  global_c\n                );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

(1) 方法一会产生无效代码:

\n
int global_a, global_b, global_c;\n\nvoid FunctionName(int a, int b, int c)\n{\n}\n\nvoid AnotherFunction()\n{\n  FunctionName(global_a, global_b, #if ((0) > 0) global_opt);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

(2) 方法 2 生成有效的 C 代码:

\n
int global_a, global_b, global_c;\n\nvoid FunctionName(int a, int b, int c)\n{\n}\n\nvoid AnotherFunction()\n{\n   FunctionName(global_a, global_b, global_c);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

对标准的哪种解释是正确的?

\n

zwo*_*wol 5

首先,您根本不能将预处理指令放入类似函数的宏的参数中,因为某些文本与您引用的内容略有不同:

\n
\n

如果参数列表中存在预处理标记序列,否则这些预处理标记将充当预处理指令,则行为未定义。

\n
\n

[ N1570,\xc2\xa76.10.3 p11 ]。

\n

其次,与此无关,该标准要求您称为 (1) 的行为。这是由您引用的这部分文本指定的:

\n
\n

在被替换之前,每个参数\xe2\x80\x99s预处理标记被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理标记。

\n
\n

如果在识别参数之间的边界之前扩展类似函数的宏的参数,则这句话没有任何意义。您还可以通过实验看到这一点,只需稍微修改一下您的代码:

\n
#if (CONDITION > 0)\n#define FunctionAlias(par_a, par_b, par_opt, par_c) \\\n          FunctionName(par_a, par_b, par_opt, par_c)\n#else\n#define FunctionAlias(par_a, par_b, par_c) \\\n          FunctionName(par_a, par_b, par_c)\n#endif\n\nint global_a, global_b, global_c;\n#if (CONDITION > 0)\nint global_opt;\n#define GLOBAL_OPT global_opt,\n#else\n#define GLOBAL_OPT /*nothing*/\n#endif\n\nvoid FunctionName(int a, int b, \n#if CONDITION > 0\n                  int opt,\n#endif\n                  int c)\n{\n}\n \nvoid AnotherFunction()\n{\n   FunctionAlias(\n                  global_a,\n                  global_b,\n                  GLOBAL_OPT\n                  global_c\n                );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

CONDITION如果未定义或为零,则可以正常编译,但是当CONDITION非零时,您将收到如下错误

\n
test.c: In function \xe2\x80\x98AnotherFunction\xe2\x80\x99:\ntest.c:28:17: error: macro "FunctionAlias" requires 4 arguments, but only 3 given\n   28 |                 );\n      |                 ^\n
Run Code Online (Sandbox Code Playgroud)\n

证明在寻找 的四个参数之前GLOBAL_OPT没有展开FunctionAlias

\n