条件包含:字符常量的数值:在 #if/#elif 内与不带 #if/#elif:为什么匹配是实现定义的?

Pav*_*kin 1 c constants language-lawyer c-preprocessor implementation-defined-behavior

案例 A:C11,6.6 常量表达式,语义,5:

如果在翻译环境中计算浮动表达式,则算术范围和精度应至少与在执行环境中计算表达式一样大。116)

这需要以下程序返回 0:

#include <float.h>

#define EXPR DBL_MIN * DBL_MAX

double d1 = EXPR;
double d2;

#pragma STDC FENV_ACCESS ON

int main(void)
{
    d2 = EXPR;
    return d1 == d2 ? 0 : 1;
}
Run Code Online (Sandbox Code Playgroud)

案例 B:C11,6.10.1 条件包含,语义,4:

这些字符常量的数值是否与表达式中(#if 或 #elif 指令除外)中出现相同字符常量时获得的值匹配是实现定义的。168)

不需要以下程序返回 0:

#define EXPR 'z' - 'a' == 25

int main(void)
{
    _Bool b1 = 0;
    _Bool b2;
#if EXPR
    b1 = 1;
#endif
    b2 = EXPR;
    return b1 == b2 ? 0 : 1;
}
Run Code Online (Sandbox Code Playgroud)

问题:制定“案例 B”实现定义行为的基本原理是什么?

Adr*_*ica 5

C11 标准(我将从这份草案文件中引用)定义了两个字符集:

\n
\n

5.2.1 字符集

\n

1 \xc2\xa0\xc2\xa0\xc2\xa0 应定义两组字符及其关联的整理序列:\n写入源文件的集(源字符集),\n以及在执行中解释的集环境(执行字符集)。每个集合进一步分为一个基本字符集(其内容由本子条款给出)和一组零个或多个特定于语言环境的成员(不是基本字符集的成员),称为扩展字符。该组合集也\n称为扩展字符集。执行字符集成员的值是由实现定义的。

\n
\n

此外,不要求这些集合中的等效字符由相同的表示,也不要求拉丁字母按顺序存储。因此,在给出的示例中,值\'z\' - \'a\'这两个集合中的值不必相同。

\n

现在,翻译阶段的顺序指定使用源字符集执行宏调用和求值(以及其他预处理指令),但在转换为执行字符集之后对可执行代码中出现的表达式进行求值

\n
\n

5.1.1.2 翻译阶段

\n

\xe2\x80\xa6
\n4. \xc2\xa0\xc2\xa0\xc2\xa0 执行预处理指令,扩展宏调用,并_Pragma执行\n一元运算符表达式。如果与通用字符名称的语法匹配的字符序列是由标记串联生成的\n(6.10.3.3),则行为未定义。预处理指令#include导致指定的头文件或源文件从阶段 1 到阶段 4 进行递归处理。所有预处理指令都将被\n删除。
\n5. \xc2\xa0\xc2\xa0\xc2\xa0 将字符常量和字符串文字中的每个源字符集成员和转义序列转换为执行字符集的相应成员;如果没有相应的成员,则\n将转换为除空(宽)字符之外的实现定义的成员。
\n\xe2\x80\xa6

\n
\n

因此,因为这些字符集之间的关系是实现定义的,并且因为基于字符的常量表达式的两次出现被定义为使用不同的集,所以它们可能具有不同的求值这一事实也必须是实现定义的

\n