读取不确定的值会调用 UB?

Lun*_*din 5 c undefined-behavior

SO 上的各种受人尊敬的高代表用户一直坚持认为读取具有不确定值的变量“始终是 UB”。那么在 C 标准中究竟在哪里提到了这一点?

很明显,不确定的值可能是未指定的值或陷阱表示:

3.19.2
indeterminate value 不确定值
或未指定值或陷阱表示

3.19.3
未指定值
本国际标准对在任何情况下选择哪个值没有强加要求的相关类型的有效值
注:未指定值不能是陷阱表示。

3.19.4
陷阱表示
不需要表示对象类型值的对象表示

同样很明显,读取陷阱表示会调用未定义的行为,6.2.6.1:

某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式并且被没有字符类型的左值表达式读取,则行为未定义。如果这种表示是由没有字符类型的左值表达式修改对象的全部或任何部分的副作用产生的,则行为是未定义的。50) 这种表示称为陷阱表示。

但是,不确定的值不一定包含陷阱表示。事实上,陷阱表示对于使用二进制补码的系统来说是非常罕见的。

C 标准中的哪个地方实际上说读取不确定的值会调用未定义的行为?

我在看C11的非规范性附件J,发现这确实被列为UB的一个案例:

具有自动存储期限的对象的值在不确定时使用(6.2.4、6.7.9、6.8)。

但是,列出的部分是无关紧要的。6.2.4 仅说明有关生命周期和变量值何时变得不确定的规则。类似地,6.7.9 是关于初始化并说明变量的值如何变得不确定。6.8 似乎几乎无关紧要。这些部分都没有包含任何规范性文本,说明访问不确定的值会导致 UB。这是附件 J 中的缺陷吗?

然而,在 6.3.2.1 中有一些关于左值的相关规范文本:

如果左值指定了一个自动存储持续时间的对象,该对象可以使用寄存器存储类声明(从未获取其地址),并且该对象未初始化(未使用初始化程序声明,并且在使用之前未对其进行赋值) ),行为未定义。

但这是一种特殊情况,它仅适用于从未被取地址的自动存储期变量。我一直认为 6.3.2.1 的这一部分是关于不确定值(不是陷阱表示)的唯一 UB 情况。但人们一直坚持“它总是UB”。那么具体是在哪里提到的呢?

The*_*kis 2

据我所知,标准中没有任何内容表明使用不确定值始终是未定义的行为。

被阐明为调用未定义行为的情况是:

  • 如果该值恰好是陷阱表示。
  • 如果不确定值是自动存储的对象。
  • 如果该值是指向生命周期已结束的对象的指针。

例如,C 标准指定该类型unsigned char没有填充位,因此它的任何值都不能是陷阱表示。

诸如此类的函数的可移植实现memcpy利用这一事实来执行任何值的复制,包括不确定值。当用作包含填充位的类型的值时,这些值可能是陷阱表示,但当用作 的值时,它们只是未指定unsigned char


我认为,假设如果某些东西可以调用未定义的行为,那么当程序没有安全的检查方法时,它确实会调用未定义的行为,这是错误的。考虑以下示例:

int read(int* array, int n, int i)
{       
   if (0 <= i)
       if (i < n)
           return array[i];
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,该read函数没有安全的方法来检查是否array确实是(至少) length n。显然,如果编译器将这些可能的 UB操作视为明确的 UB,则几乎不可能编写任何指针代码。

更一般地说,如果编译器无法证明某个东西是 UB,它就必须假设它不是 UB,否则就有破坏一致性程序的风险。


唯一将可能性视为确定性的情况是自动存储对象的情况。我认为可以合理地假设其原因是因为这些情况可以被静态拒绝,因为编译器所需的所有信息都可以通过本地流分析获得。

另一方面,将其声明为非自动存储对象的 UB 不会为编译器提供任何在优化或可移植性方面有用的信息(在一般情况下)。因此,该标准可能不会提及这些情况,因为无论如何它都不会改变实际实现中的任何内容。