复制一个一些成员未初始化的结构体是否有效?
我怀疑这是未定义行为,但如果是这样的话,即使那些成员从未被直接使用,留下任何未初始化的结构体成员也会非常危险。因此,我想知道标准中是否有允许这样做的内容。
例如,下面的代码是否有效?
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;
}
如果未初始化的成员不是无符号窄字符类型或std::byte
,那么使用隐式定义的复制构造函数复制包含此不确定值的结构体就是技术上未定义行为,对于相同类型的具有不确定值的变量进行复制也是如此,原因是由于[dcl.init]/12。
这也适用于这里,因为除了union
之外,隐式生成的复制构造函数被定义为按照直接初始化的方式单独复制每个成员,参见[class.copy.ctor]/4。
这也是活跃的CWG问题2264的主题。
虽然我想实际上你不会遇到任何问题。
如果您想要100%确定,如果类型是trivially copyable,则始终使用std :: memcpy
具有良好定义的行为,即使成员具有不确定的值。
除了这些问题外,您应该始终在构造时使用指定的值正确初始化类成员,假设您不需要类具有trivial default constructor。您可以轻松使用默认成员初始化程序语法来例如对成员进行值初始化:
struct Data {
int a{}, b{};
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
memcpy
的方式复制对象表示,即使对于可平凡复制的类型也是如此。唯一的例外是联合体,隐式复制构造函数确实会按照memcpy
的方式复制对象表示。 - walnut浮点类型可能有Signalling NaNs,一些平台上整数可能具有Trap Representation。如果对象表示不代表任何对象类型的值,则称其为Trap Representation。以任何方式访问Trap Representation(除了通过字符类型的Lvalue表达式读取它)都是未定义行为。
memcpy
复制对象的原始表示。这样做是安全的,因为对象的值不被解释,而是复制对象表示的原始字节序列。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;
}
arr
中列出的x.dat
或y.dat
的任何元素的值,则可以对代码进行优化: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
分配的内存区域中字节的值。
uint1 = ushort1; ... if (uint1 < 70000) foo[uint1] = 123;
,编译器可能会生成代码,在该路径上始终使uint1
小于70000,它可能会生成代码,其中uint1
可能会保存一个大于69999的值,但如果是,则执行比较并跳过赋值,或者它可能... - supercatuint1
可能会保存一个大于 70000 的值,但如果没有定义 uint1
可以这么大的方式,就无条件执行赋值操作。不幸的是,标准术语无法让编译器在程序员不关心 uint1
将保存什么值的情况下选择前两种方法中最有效的一种,但不允许编译器同时生成可能将 uint1
设置为大于 65535 的值的代码,并假设 uint1
永远不会接收到这样的值。 - supercat