8

在 C++14 标准 (n3797) 中,关于左值到右值转换的部分内容如下(强调我的):

4.1 左值到右值的转换[conv.lval]

  1. 非函数、非数组类型的 glvalue (3.10)T可以转换为纯右值。如果T是不完整类型,则需要进行此转换的程序格式错误。如果T是非类类型,则纯右值的类型是 的 cv 非限定版本T。否则纯右值的类型是T

  2. 当在未计算的操作数或其子表达式中发生左值到右值的转换(第 5 条)时,不访问包含在引用对象中的值。在所有其他情况下,转换结果根据以下规则确定:

    • 如果T是一个(可能是 cv 限定的)std::nullptr_t,那么结果是一个空指针常量。
    • 否则,如果T具有类类型,则转换复制初始化T来自泛左值的临时类型,并且转换的结果是临时的纯右值。
    • 否则,如果泛泛值引用的对象包含无效的指针值,则行为是实现定义的。
    • 否则,如果T是(可能是 cv 限定的)无符号字符类型,并且泛左值所引用的对象包含不确定值,并且该对象没有自动存储持续时间,或者泛左值是一元运算符的操作数,&或者它是绑定到一个引用,结果是一个未指定的值。
    • 否则,如果泛左值引用的对象具有不确定的值,则行为未定义。
    • 否则,glvalue 指示的对象是纯右值结果。
  3. [注:另见 3.10]

这一段的意义是什么(粗体)?

如果这一段不在这里,那么它适用的情况将导致未定义的行为。通常,我希望unsigned char在具有不确定值的情况下访问一个值会导致未定义的行为。但是,这一段意味着

  • 如果我实际上并没有访问字符值,即我立即将它传递给&或将其绑定到引用,或者
  • 如果unsigned char没有自动存储期限,

然后转换产生一个未指定的值,而不是未定义的行为。

我是否正确得出以下结论:该程序:

#include <new>
#include <iostream>

// using T = int;
using T = unsigned char;

int main() {
  T * array = new T[500];
  for (int i = 0; i < 500; ++i) {
    std::cout << static_cast<int>(array[i]) << std::endl;
  }
  delete[] array;
}

由标准明确定义,并且必须输出 500 个未指定整数的序列,而同一个程序 whereT = int会有未定义的行为?


IIUC,使其 UB 读取具有不确定值的内容的原因之一是允许优化器积极消除死存储。因此,本段可能意味着符合标准的编译器在unsigned char使用unsigned char.

假设我理解正确,这条规则的基本原理是什么?何时能够读取unsigned char具有不确定值并获得未指定结果而不是 UB 有用?我有这样的感觉,如果他们在制定这部分规则时付出了这么多努力,他们就有动力帮助他们关心的某些代码示例,或者与标准的其他部分保持一致,或者简化其他问题. 但我不知道那可能是什么。

4

1 回答 1

1

在许多情况下,代码将编写 PODS 或数组的某些部分而不编写所有内容,然后使用类似memcpyor之类的函数fwrite来复制或编写整个事物,而无需考虑哪些部分已赋值,哪些未赋值。尽管 C++ 代码使用基于字节的操作来复制或写出聚合的内容并不常见,但这样做的能力是该语言的基本部分。要求程序将确定的值写入对象的所有部分,包括那些永远不会“关心”的部分,将不必要地损害效率。

于 2017-09-06T16:36:07.770 回答