我以前使用联合体感到很自在;今天当我读到这篇文章后,我感到很震惊,因为我发现这段代码存在风险。
union ARGB
{
uint32_t colour;
struct componentsTag
{
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
} components;
} pixel;
pixel.colour = 0xff040201; // ARGB::colour is the active member from now on
// somewhere down the line, without any edit to pixel
if(pixel.components.a) // accessing the non-active member ARGB::components
访问联合体的非最近写入成员实际上是未定义的行为,即会导致未定义的行为。如果这不是联合体预期的用法,那么什么是呢?有人可以详细解释一下吗?
更新:
事后我想澄清几件事情。
- 对于 C 和 C++,问题的答案并不相同;我的无知年少时标记了两个标签。
- 在研究过 C++11 的标准后,我无法得出结论:访问/检查非活动联合成员是否未定义/未指定/实现定义。我能找到的只有 §9.5/1:
如果一个标准布局联合包含共享公共初始序列的多个标准布局结构,并且此标准布局联合类型的对象包含标准布局结构之一,则允许检查任何标准布局结构成员的公共初始序列。 §9.2/19:如果对应成员具有布局兼容类型且没有成员是位字段或两个成员是具有一个或多个初始成员相同宽度的位字段,则两个标准布局结构共享一个公共初始序列。
- 而在 C 中,(C99 TC3 - DR 283以及之后的版本)是合法的 (感谢 Pascal Cuoq 指出这一点)。但是,如果读取的值恰好是类型不合法的值(称为“陷阱表示”),则尝试执行仍可能导致未定义行为。否则,读取的值是实现定义的。
C89/90 在未指定的行为下对此进行了说明(Annex J),K&R 的书称其为实现定义。来自 K&R 的引用:
这是联合的目的——可以合法地容纳多种类型之一的单个变量。只要使用是一致的:检索的类型必须是最近存储的类型。程序员有责任跟踪哪种类型当前存储在联合中;如果将某些东西存储为一种类型并提取为另一种类型,则结果取决于实现。
从 Stroustrup 的 TC++PL 中提取(强调我的)
使用联合对于数据的兼容性可能是必不可少的[...]有时会被误用于“类型转换”。
最重要的是,这个问题(标题自问以来仍未更改)的目的是了解联合的目的,而不是标准允许什么例如,当然可以使用继承进行代码重用,但是将其作为 C++ 语言特性引入的目的或原意并非如此。这就是 Andrey 的答案继续保持为被接受的原因。