将联合体首成员reinterpret_cast为活跃成员类型是否属于C++未定义行为?
结论
你给出的这段访问操作属于未定义行为,C++标准没有对该操作的行为做明确定义。
具体分析
首先我们先梳理代码中已确认合法的部分:
struct A { int i; }; struct B { std::string s; }; struct C { union { A a{}; B b; }; }; void foo() { C c; // 匿名联合体默认初始化,a是活跃成员,符合标准要求 c.b = {}; // 对b的赋值操作自动将b切换为活跃成员,这一步本身合法 // 问题出在下面这行代码 auto& b = reinterpret_cast<B&>(c.a); }
问题出在reinterpret_cast<B&>(c.a)这行,主要有两个违反C++标准的点:
- 违反严格别名规则:C++明确规定,对象的存储只能通过和对象类型兼容的左值访问,兼容类型包括和对象相同的类型、带const/volatile等限定的相似类型、
char/unsigned char/std::byte三类字符类型。A和B是完全无关的自定义类型,不属于上述任意一种兼容范畴,直接将A类型的左值c.a转成B类型引用访问B对象,属于典型的别名违规。 - 违反联合体成员访问规则:执行
c.b = {}后联合体的活跃成员已经变为b,c.a是失效的非活跃成员,直接对非活跃成员做类型转换来访问另一个成员,不符合标准对联合体访问的约束。就算你明确知道联合体所有成员的起始地址相同,也不能直接这么操作,合法的跨成员地址转换必须基于联合体本身的指针/引用做转换,而不是基于非活跃成员的左值。
如果确实需要通过联合体的存储拿到当前活跃的b的引用,合法的写法如下(仅作语法合法演示,实际工程不推荐这种跨成员访问的写法):
auto& b = std::launder(reinterpret_cast<B&>(c));
这个写法基于联合体本身的左值做转换,符合C++20及之后版本的标准要求,是明确定义的。
内容的提问来源于stack exchange,提问作者Timo
相关产品推荐
相关产品推荐

