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

Copying structs with uninitialized members

本文关键字:初始化 成员 结构 复制      更新时间:2023-10-16

复制某些成员未初始化的结构有效吗?

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

例如,这有效吗?

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

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

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

这也是现行《禁止化学武器公约》第2264期的主题。

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

如果您想100%确定,即使成员具有不确定的值,如果类型是一般可复制的,则使用std::memcpy始终具有定义良好的行为。


撇开这些问题不谈,无论如何,您都应该在构造时使用指定的值正确初始化类成员,假设您不需要类具有琐碎的默认构造函数。您可以使用默认的成员初始值设定项语法轻松完成,例如值初始化成员:

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

通常,复制未初始化的数据是未定义的行为,因为该数据可能处于陷阱状态。引用此页面:

如果对象表示不表示对象类型的任何值,则称为陷阱表示。通过字符类型的左值表达式以外的任何方式访问陷阱表示都是未定义的行为。

浮点类型可以使用信号NaN,在某些平台上,整数可能具有陷阱表示。

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

在某些情况下,例如所描述的情况,C++标准允许编译器以客户认为最有用的方式处理构造,而不要求行为是可预测的。换句话说,这样的构造会调用"未定义的行为"。然而,这并不意味着这样的构造是"禁止的",因为C++标准明确放弃了对"允许"良好程序做什么的管辖权。虽然我不知道C++标准有任何公开的基本原理文件,事实上,它描述了与C89非常相似的未定义行为,这表明其意图是相似的:"未定义的行为允许实现者不捕捉某些难以诊断的程序错误。它还确定了可能的一致语言扩展领域:实现者可以通过提供官方未定义行为的定义来增强语言"。

在许多情况下,最有效的处理方法是编写下游代码关心的结构部分,而忽略下游代码不关心的部分。要求程序初始化结构的所有成员,包括那些什么都不关心的成员,将不必要地阻碍效率。

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

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所有成员的精确"逐位复制"。所以data2.b的值将和data.b的值完全相同。然而,data.b的确切值是无法预测的,因为您并没有明确地初始化它。它将取决于为data分配的存储器区域中的字节的值。