1

案例 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;
}

案例 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;
}

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

4

1 回答 1

5

C11 标准(我将引用此文档草案)定义了两个字符集:

5.2.1 字符集

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

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

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

5.1.1.2 翻译阶段

…<br/> 4. 执行预处理指令,扩展宏调用,_Pragma执行一元运算符表达式。如果与通用字符名称的语法匹配的字符序列由标记连接 (6.10.3.3) 生成,则行为未定义。预处理指令导致命名的#include头文件或源文件从阶段 1 到阶段 4 以递归方式进行处理。然后删除所有预处理指令。
5. 字符常量和字符串文字中的每个源字符集成员和转义序列都被转换为执行字符集的对应成员;如果没有对应的成员,则将其转换为除空(宽)字符之外的实现定义的成员。
…<br/>

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

于 2022-02-01T13:44:24.363 回答