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

C/C++: How should preprocessor directive work on macros argument list?

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

16.3.1 Argument substitution (C++11)

6.10.3.1 Argument substitution (C11)

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

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

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

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

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

#define CONDITION (0)

#if (CONDITION > 0)
#define FunctionAlias(par_a, par_b, par_opt, par_c) \
          FunctionName(par_a, par_b, par_opt, par_c)
#else
#define FunctionAlias(par_a, par_b, par_c) \
          FunctionName(par_a, par_b, par_c)
#endif

int global_a, global_b, global_c;
#if (CONDITION > 0)
int global_opt;
#endif

void FunctionName(int a, int b, int c)
{
}
 
void AnotherFunction()
{
   FunctionAlias(
                  global_a,
                  global_b,
                  #if (CONDITION > 0)
                  global_opt,
                  #endif
                  global_c
                );
}

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

int global_a, global_b, global_c;

void FunctionName(int a, int b, int c)
{
}

void AnotherFunction()
{
  FunctionName(global_a, global_b, #if ((0) > 0) global_opt);
}

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

int global_a, global_b, global_c;

void FunctionName(int a, int b, int c)
{
}

void AnotherFunction()
{
   FunctionName(global_a, global_b, global_c);
}

哪些标准的解释是正确的?

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

One can interpret this paragraph as if the standard required to:

(1) At first identify macro arguments (comma separated) and then expand all the macros contained in each argument separately,

or

(2) Expand all the macros contained in the argument list and then identify each argument.

我有点明白你是从哪里来的,但我并没有真正理解你是如何得出 (2) 作为对文本的合理解释的。您引用的部分首先指定参数替换发生“在确定调用function-like宏的参数后,参数替换发生”,并且其他一切都是对参数替换所包含的内容的描述。这包括为在大多数情况下 macro-expanding 它们实际替换而准备的参数。 (1)是正确的解释。

但是请注意,宏扩展是一个迭代过程。粗略地说,宏扩展被 重新扫描 以进行进一步的宏替换,包括如引用文本中所述执行的宏参数扩展。这就是为什么扩展每个参数的预处理标记“就好像它们构成了预处理文件的其余部分”是很重要的。这不仅将每个参数与主机宏调用周围的源文本隔离开来,而且最重要的是将其与其他参数及其扩展隔离开来。

另请注意,如果您发现标准不明确,您可以自己测试一下。您甚至已经生成了测试用例。 转念一想,您 没有 提供有效的测试用例,因为预处理器指令可能不会出现在宏调用的参数列表中.因此,该标准对预处理示例源的结果没有任何说明。

首先,您根本不能将预处理指令放入 function-like 宏的参数中,因为某些文本与您引用的内容相比有所下降:

If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, the behavior is undefined.

[N1570, §6.10.3 p11].

其次,独立于此,标准需要您称为 (1) 的行为。这是由您引用的这部分文字指定的:

Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

如果 function-like 宏的参数在确定参数之间的边界之前展开,这句话就没有任何意义。您也可以通过实验看到这一点,稍微修改一下您的代码:

#if (CONDITION > 0)
#define FunctionAlias(par_a, par_b, par_opt, par_c) \
          FunctionName(par_a, par_b, par_opt, par_c)
#else
#define FunctionAlias(par_a, par_b, par_c) \
          FunctionName(par_a, par_b, par_c)
#endif

int global_a, global_b, global_c;
#if (CONDITION > 0)
int global_opt;
#define GLOBAL_OPT global_opt,
#else
#define GLOBAL_OPT /*nothing*/
#endif

void FunctionName(int a, int b, 
#if CONDITION > 0
                  int opt,
#endif
                  int c)
{
}
 
void AnotherFunction()
{
   FunctionAlias(
                  global_a,
                  global_b,
                  GLOBAL_OPT
                  global_c
                );
}

如果 CONDITION 未定义或为零,这将编译正常,但当 CONDITION 为非零时,您将收到类似

的错误
test.c: In function ‘AnotherFunction’:
test.c:28:17: error: macro "FunctionAlias" requires 4 arguments, but only 3 given
   28 |                 );
      |                 ^

证明 GLOBAL_OPT 在查找 FunctionAlias.

的四个参数之前 展开