复制一个未初始化其成员的结构是否有效?
我怀疑这是未定义的行为,但如果是这样,则会使任何未初始化的成员保留在结构中(即使从未直接使用这些成员)也很危险。所以我想知道标准中是否有允许它的东西。
例如,这有效吗?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
复制一个未初始化其成员的结构是否有效?
我怀疑这是未定义的行为,但如果是这样,则会使任何未初始化的成员保留在结构中(即使从未直接使用这些成员)也很危险。所以我想知道标准中是否有允许它的东西。
例如,这有效吗?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
Answers:
是的,如果未初始化的成员不是unsigned窄字符类型或std::byte
,那么使用隐式定义的copy构造函数复制包含该不确定值的结构在技术上是未定义的行为,因为它是用于复制具有相同类型的不确定值的变量,因为的[dcl.init] / 12。
这适用于这里,因为除union
s 外,隐式生成的副本构造函数被定义为像通过直接初始化一样分别复制每个成员,请参见[class.copy.ctor] / 4。
这也是现行CWG问题2264的主题。
我想在实践中您不会对此有任何问题。
如果您想100%确定,std::memcpy
则即使类型具有不确定的值,如果类型是可微复制的,使用总是具有明确定义的行为。
除了这些问题,假设您不需要该类具有琐碎的默认构造函数,则无论如何都要在构造时始终使用指定的值正确初始化类成员。您可以使用默认的成员初始化程序语法轻松进行此操作,例如,对成员进行值初始化:
struct Data {
int a{}, b{};
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
memcpy
,即使对于普通可复制类型也是如此。唯一的例外是联合,其隐式副本构造函数的确复制对象表示形式,就像by一样memcpy
。
通常,复制未初始化的数据是未定义的行为,因为该数据可能处于陷阱状态。引用此页面:
如果对象表示形式不表示该对象类型的任何值,则称为陷阱表示形式。除了通过字符类型的左值表达式读取陷阱表示以外,以任何其他方式访问陷阱表示都是未定义的行为。
对于浮点类型,可能会发信号通知NaN,并且在某些平台上,整数可能具有陷阱表示形式。
但是,对于普通可复制类型,可以使用它memcpy
来复制对象的原始表示形式。这样做是安全的,因为不解释对象的值,而是复制对象表示形式的原始字节序列。
unsigned char[64]
)如何处理?将结构的字节视为具有未指定的值可能会不必要地阻碍优化,但是要求程序员手动用无用的值填充数组会进一步阻碍效率。
在某些情况下,例如所描述的那种情况,C ++标准允许编译器以其客户认为最有用的任何方式处理构造,而无需要求行为可预测。换句话说,此类构造调用“未定义行为”。但是,这并不意味着这些构造应被“禁止”,因为C ++标准明确放弃了对“允许”执行的程序的管辖权。虽然我不知道任何已发布的C ++标准的Rationale文档,但它实际上像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.dat
或未在y.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
。
std::memcpy
。这些都不能阻止使用std::memcpy
或std::memmove
。它仅防止使用隐式副本构造函数。