使用memcpy构造可平凡复制union是否属于UB?其活跃成员如何判定?
问题解答
结论先行
你给出的示例在C17及更高版本标准下完全合法,不会触发未定义行为;如果需要兼容C14及更早标准,补充一行显式激活union成员的代码即可完全规避风险。
具体规则说明
- 首先
Bar属于平凡可复制类型,这是memcpy操作合法的核心前提:对于所有平凡可复制类型,只要源对象的对象表示有效,用memcpy将其对象表示复制到目标类型的存储中后,目标对象就会拥有和源对象一致的值表示,这个操作本身是标准明确支持的。你示例中src是有效int对象,大小和Bar的大小完全一致,因此memcpy操作本身没有任何问题。 - 关于union活跃成员的常见误解:并不是“最先读取的成员自动成为活跃成员”,C++17新增了明确规则:如果union当前存储的值表示和某个成员的类型匹配,那么读取该成员的操作是合法的,会隐式创建该成员作为union的活跃成员。你示例中的
Bar两个成员,num是int类型,foo是底层类型为int的枚举类型,两者的对象表示完全一致,你memcpy写入的就是合法int的对象表示,因此后续无论读取num还是foo,都是符合标准的合法操作。 - 关于
BOOST_IS_BITWISE_SERIALIZABLE特性的使用:你的推测是正确的,这个宏的作用就是标记类型允许直接通过二进制复制完成序列化/反序列化,底层确实依赖memcpy类操作。对于你用到的这类平凡可复制union,只要反序列化后读取的成员类型和序列化时的活跃成员类型兼容,就完全符合该宏的使用要求,不会有问题。
旧标准兼容方案
如果需要兼容C++17之前的标准,只需要在memcpy操作之后,显式激活你要读取的成员即可,示例修改如下:
int baz(int src) { Bar dst; std::memcpy(&dst, &src, sizeof(Bar)); // 显式激活对应成员,兼容C++14及更早标准 if (src > 42) { new (&dst.num) int; // 定位new,无额外开销,仅标记活跃成员 return dst.num; } else { new (&dst.foo) Foo; return (int)dst.foo; } }
内容的提问来源于stack exchange,提问作者StableGeneous
相关产品推荐
相关产品推荐

