复制具有未初始化成员的结构

Copying structs with uninitialized members

复制部分成员未初始化的结构是否有效?

我怀疑这是未定义的行为,但如果是这样,那么将任何未初始化的成员留在结构中(即使从未直接使用这些成员)会非常危险。所以我想知道标准中是否有允许它的东西。

例如,这个有效吗?

struct Data {
  int a, b;
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

一般来说,复制未初始化的数据是未定义的行为,因为该数据可能处于陷阱状态。引用 this 页:

If an object representation does not represent any value of the object type, it is known as trap representation. Accessing a trap representation in any way other than reading it through an lvalue expression of character type is undefined behavior.

对于浮点类型和某些平台上的整数 may have 陷阱表示,信号 NaN 是可能的。

但是,对于 trivially copyable 类型,可以使用 memcpy 复制对象的原始表示。这样做是安全的,因为不解释对象的值,而是复制对象表示的原始字节序列。

是的,如果未初始化的成员不是无符号窄字符类型或 std::byte,那么使用隐式定义的复制构造函数复制包含此不确定值的结构在技术上是未定义的行为,因为它用于复制由于 [dcl.init]/12.

,具有相同类型不确定值的变量

这适用于此,因为除了 unions 之外,隐式生成的复制构造函数被定义为单独复制每个成员,就像通过直接初始化一样,参见 [class.copy.ctor]/4

这也是活动的主题CWG issue 2264

不过我想在实践中你不会有任何问题。

如果您想 100% 确定,如果类型为 trivially copyable,使用 std::memcpy 始终具有明确定义的行为,即使成员具有不确定的值。


撇开这些问题不谈,假设您不需要 class 拥有 trivial default constructor,无论如何您都应该始终在构造时使用指定值正确初始化您的 class 成员。您可以使用默认的成员初始化语法轻松地做到这一点,例如值初始化成员:

struct Data {
  int a{}, b{};
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

在某些情况下,例如所描述的情况,C++ 标准允许编译器以客户认为最有用的任何方式处理构造,而不要求行为是可预测的。换句话说,这样的构造调用 "Undefined Behavior"。然而,这并不意味着这样的构造是 "forbidden" 因为 C++ 标准明确放弃了对格式良好的程序 "allowed" 做什么的管辖权。虽然我不知道任何已发布的 C++ 标准基本原理文档,但它描述未定义行为的事实与 C89 非常相似,这表明预期的含义是相似的:"Undefined behavior gives the implementor license not to catch certain program errors that are difficult to diagnose. It also identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior".

在许多情况下,最有效的处理方式是编写下游代码将关心的结构部分,同时省略下游代码不关心的部分。要求程序初始化结构的所有成员,包括那些永远不会关心的成员,会不必要地阻碍效率。

此外,在某些情况下,让未初始化的数据以非确定性方式运行可能是最有效的。例如,给定:

struct q { unsigned char dat[256]; } x,y;

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
    temp.dat[arr[i]] = i;
  x=temp;
  y=temp;
}

如果下游代码不关心 x.daty.dat 的任何元素的值,其索引未在 arr 中列出,则代码可以优化为:

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
  {
    int it = arr[i];
    x.dat[index] = i;
    y.dat[index] = i;
  }
}

如果要求程序员在复制之前显式编写 temp.dat 的每个元素,包括那些下游不关心的元素,那么效率的提高是不可能的。

另一方面,在某些应用程序中,避免数据泄露的可能性很重要。在这样的应用程序中,拥有一个代码版本可能是有用的,该代码版本被检测以捕获任何复制未初始化存储的尝试,而不考虑下游代码是否会查看它,或者实现保证任何存储可能是有用的其内容可能被泄露的内容将被清零或以其他方式被非机密数据覆盖。

据我所知,C++ 标准并未试图说明这些行为中的任何一个比其他行为更有用,以证明强制执行它是合理的。具有讽刺意味的是,这种规范的缺失可能是为了促进优化,但如果程序员不能利用任何类型的弱行为保证,任何优化都将被否定。

由于 Data 的所有成员都是基本类型,data2 将得到 data 的所有成员的 "bit-by-bit copy"。所以 data2.b 的值将与 data.b 的值完全相同。但是,无法预测 data.b 的确切值,因为您尚未明确初始化它。这将取决于为 data.

分配的内存区域中字节的值