提问人:Tomek Czajka 提问时间:2/7/2020 最后编辑:L. F.Tomek Czajka 更新时间:2/16/2020 访问量:2278
复制具有未初始化成员的结构
Copying structs with uninitialized members
问:
复制一些成员未初始化的结构是否有效?
我怀疑这是未定义的行为,但如果是这样,它会使在结构中留下任何未初始化的成员(即使这些成员从未直接使用)非常危险。所以我想知道标准中是否有东西允许它。
例如,这有效吗?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
答:
通常,复制未初始化的数据是未定义的行为,因为该数据可能处于捕获状态。引用此页面:
如果对象表示不表示对象类型的任何值,则称为陷阱表示。通过字符类型的左值表达式读取陷阱表示之外,以任何其他方式访问陷阱表示都是未定义的行为。
对于浮点类型,可以信令 NaN,在某些平台上,整数可能具有陷阱表示形式。
但是,对于简单可复制的类型,可以使用它来复制对象的原始表示形式。这样做是安全的,因为不会解释对象的值,而是复制对象表示的原始字节序列。memcpy
评论
是的,如果未初始化的成员不是无符号窄字符类型或 ,则使用隐式定义的复制构造函数复制包含此不确定值的结构在技术上是未定义的行为,就像复制具有相同类型的不确定值的变量一样,因为 [dcl.init]/12。std::byte
这适用于此处,因为隐式生成的复制构造函数(除 s 外)被定义为单独复制每个成员,就像通过直接初始化一样,请参见 [class.copy.ctor]/4。union
这也是正在进行的 CWG 第 2264 期的主题。
不过,我想在实践中你不会有任何问题。
如果要 100% 确定,如果类型是可复制的,则即使成员具有不确定的值,则 using 始终具有明确定义的行为。std::memcpy
撇开这些问题不谈,无论如何,您都应该始终在构造时使用指定的值正确地初始化您的类成员,假设您不需要类具有简单的默认构造函数。您可以使用默认成员初始值设定项语法轻松做到这一点,例如对成员进行值初始化:
struct Data {
int a{}, b{};
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
评论
memcpy
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.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
评论
uint16_t
uint1 = ushort1; ... if (uint1 < 70000) foo[uint1] = 123;
uint1
uint1
评论