题
我已经使用工会的早期舒适;今天,我感到吃惊的是,当我读 这个职位 和才知道,这个代码
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和C++。
- 之后,通过精练C++11的标准,我不能确凿地说,它要求出访问/检查的一种非活性联盟成员是不确定的/未指定/执行定义。所有我能找到的§9.5/1:
如果一个标准布局联盟包含若干标准布局的结构,分享一个共同的初始序列,如果一个象这一标准布局联盟类型包含一个标准布局的结构,允许检查的共同的初始序列的任何标准布局结构的成员。§9.2/19:两种标准结构布局分享一个共同的初始顺序,如果应成员拥有的布局,兼容类型和两件既不是一位域或两位领域带宽度相同的序列的一个或更多的初始成员。
- 同时在C,(C99TC3-DR283 起)这是法律这样做(感谢帕斯卡尔Cuoq 为将这一)。但是,试图做的 它仍然可以导致不明确的行为, 如果读取的数值是无效的(所谓的"陷阱表示")的类型读通过。否则,值读是定义的实现。
C89/90称这种下未指定的行为(附件J)和K&R书说,它的定义的实现。引自K&R:
这是一个联盟-一个单一的变量,可以合法持有任何种类型。[...]只要使用情况是一致的:种类检索必须的类型最近存储。这是程序员的责任跟踪这类是目前存储在一个联盟;结果依赖于实现的,如果事情被储存作为一种类型,并提取作为另一个。
从中提取Stroustrup的TC++PL(强调地雷)
使用的工会可以是重要compatness的数据[...] 有时候被滥用的"类型转换".
上述所有,这个问题(其标题保持不变,因为我的要求)提出的意图的理解的目的,工会和不上什么标准可以允许 E.g。使用的继承码的再利用,当然,允许通过C++标准,但是 这不是目的或原来的意图引入继承作为一个C++的语言功能.这的原因是,安德烈的答复将继续作为接受的一个。
解决方案
目的工会是显而易见的,但由于某些原因,人们错过它很经常。
目的联盟 保存记忆 通过使用相同的存储区域,用于储存不同的对象,在不同的时间。 就是这样。
它就像一个房间的酒店。不同的人生活在其中的非重叠一段时间。这些人从来没有满足,并且一般不知道任何有关对方。通过适当的管理的时间分摊的房间(即通过确保不同的人没有得到分配给一个房间的同时)相对较小的酒店可以提供住宿,以相对较大数量的人,这是什么样的酒店。
这正是什么样的联盟。如果你知道几个对象在你的程序中保持的价值观与非重叠值的一生,然后你可以"合并"这些物体进入一个联盟,从而节省的存储器。就像一个酒店房间具有至多一个"积极的"租户在每一时刻的时候,一个联盟具有至多一个"积极的"成员在每一时刻的程序的时间。只有"积极的"成员可阅读。通过编写入其他成员,你开关的"活跃"状态,其他成员。
由于某些原因,这个原始宗旨的联盟得到了"复盖"完全不同的东西:写有一个成员的一个联盟,然后检查它通过另一个成员。这种存储器重新解释(又名"类punning")是 没有一个有效的使用的工会。这通常导致不确定的行为 被描述为制造的执行情况定义的行为在C89/90.
编辑: 使用工会的目的类型punning(即写一个成员,然后阅读另一)被赋予了更详细的定义在一个技术更正C99标准(见 博士#257 和 博士#283).然而,牢记,正式这并不保护你的行为不确定的行为通过试图读一个陷阱表示。
其他提示
你可以使用的工会创建的结构如下,其中包含一个领域,告诉我们,哪些组成部分的联盟实际上是用于:
struct VAROBJECT
{
enum o_t { Int, Double, String } objectType;
union
{
int intValue;
double dblValue;
char *strValue;
} value;
} object;
该行为不确定的语言的观点。考虑,不同的平台,可以有不同的限制,在存储器对准和字节序.代码在big endian与little endian机会更新的价值观的结构不同。固定的行为在语言将要求所有实现中使用相同的字节序(和存储器中准的约束...)限制使用。
如果你是用C++(你是采用两种标签)和你真正关心的便携性,然后你就可以使用的结构,并提供一定者需要的 uint32_t
和集领域通过适当位的操作。同样可以做到在C功能。
编辑:我期待AProgrammer写下一个答案的投票权和接近这一个。正如一些评论意见已经指出,字节序处理的其他部分的标准,让每个实施的决定要做什么,并对准和填补也可以以不同的方式处理。现在,严格的混淆规则,AProgrammer含蓄地指是一个重要的一点在这里。编译是允许作出假设的修改(或缺乏修改)的变量。在这种情况下的联盟,编译器可能重新排序的指令和移动阅读的每一个颜色组在编写的颜色变量。
最 常见的 使用 union
我经常遇到是 锯齿.
考虑以下几点:
union Vector3f
{
struct{ float x,y,z ; } ;
float elts[3];
}
这是什么做的?它允许干净,整齐的访问的一个 Vector3f vec;
's成员通过 要么 名称:
vec.x=vec.y=vec.z=1.f ;
或者通过整数进入阵列
for( int i = 0 ; i < 3 ; i++ )
vec.elts[i]=1.f;
在某些情况下,访问的名称是最清楚的事情可以做。在其他情况下,尤其是当轴是选择的编程方式,更容易的事情要做的就是访问的轴的数值指数-0x,1y,2z。
正如你所说,这是有严格定义的行为,虽然它将"工作"的多平台。真正的原因是使用工会是创造变的记录。
union A {
int i;
double d;
};
A a[10]; // records in "a" can be either ints or doubles
a[0].i = 42;
a[1].d = 1.23;
当然,你也需要某种形式的鉴别说什么变体实际上包含的内容。并请注意在C++的工会没有多少用处,因为他们可以只包含POD类型有效地那些没有造和析构函数。
C这是一个很好的方式来实现的东西就像一个变体。
enum possibleTypes{
eInt,
eDouble,
eChar
}
struct Value{
union Value {
int iVal_;
double dval;
char cVal;
} value_;
possibleTypes discriminator_;
}
switch(val.discriminator_)
{
case eInt: val.value_.iVal_; break;
在小存这种结构是用较少的内存于一个结构,具有所有成员。
通过这种方式C的提供
typedef struct {
unsigned int mantissa_low:32; //mantissa
unsigned int mantissa_high:20;
unsigned int exponent:11; //exponent
unsigned int sign:1;
} realVal;
访问位的价值观。
虽然这是有严格定义的行为,在实践中会工作的几乎任何编译器。它是这样一种广泛使用的模式,任何有自尊的编译器将需要做"正确的事"的情况下如此。它肯定是首选的类型-punning,这可能会产生破码的一些编译器。
在C++ 提高变 实现一个安全的版本的联盟,旨在防止未定义的行为尽可能多的。
其表现是相同的 enum + union
建造(堆分配的太等等),但它使用的模板清单的类型,而不是的 enum
:)
该行为可能是不确定的,但是,这只是意味着没有"标准"。所有体面的编纂者提供 #标注 以控制包装和校准,但可能有不同的默认。默认也将发生变化取决于优化的设置使用。
此外,工会不是 只是 为节省空间。他们可以帮助编译器现代化的类型punning.如果你 reinterpret_cast<>
一切都编译器不能假设你在做什么。它可能不得不扔掉了什么它知道关于你的类型和启动再次(强迫写回忆,这非常没有效率,这些天比CPU时钟的速度)。
从技术上讲这是不确定的,但在现实中大多数(?) 汇编者把它完全相同的作用 reinterpret_cast
从一种类型的其他结果执行定义。我不会失去睡眠超过当前的代码。
一个例子实际使用的工会,CORBA框架将物体使用的标记的联盟的方法。所有用户定义的类的成员之一(大)的联盟,以及一个 整的标识符 告诉demarshaller如何解释该联盟。
其他人已经提到的结构差异(little-big endian).
我读的问题,由于储存的变量是共享的,然后通过编写一,其他变化,取决于它们的类型,值可能是毫无意义的。
例如。联盟{ 浮f;int;}x;
写x。我将毫无意义,如果你再读x。f-除非是你打算在了看看标志、指数或尾数组成部分浮动。
我认为那里也是一个问题的准:如果一些变数必须词对齐,然后你可能不会获得预期的结果。
例如。联盟{ char c[4];int;}x;
如果假设在一些机char必须词对准则c[0]和c[1]将共享存储但我不c[2]和c[3].
在C语言,因为它记录了在1974年,所有构成员共用一个共同名字空间,该含义的"ptr->件"的 定义 作为加入 会员的排"ptr"和访问所得到的地址使用 部件的类型。这种设计成可以使用相同的ptr与成员 名字取自不同的结构定义,但具有相同的偏移;程序员使用这种能力对于各种各样的目的。
当构成员被分配给他们自己的名字空间,它成为不可能 宣布两个结构的成员相同的位移。加入工会 语言,使它能够实现相同的语义了 可以在早期版本的语言(虽然不能 名称出口到一个封闭的上下文可能仍有必要使用一个 找到的/替换更换foo->件foo->1.部件)。是什么 重要的不是更多的人加入工会有任何特别的 目标使用记在心,而是他们提供一个手段,其中程序员 谁不得不依靠先前的语义, 不管出于什么目的, 应该仍然 能够达到相同的语义,即使他们不得不使用一个不同的 语法做到这一点。
你可以 使用 一个联盟主要有两个原因:
- 一个方便的方式来访问的同样的数据以不同的方式,就像在你的例子
- 一种方法保存空间时也有不同的数据的成员,其中只有一个以往任何时候都可以"活跃"
1真的是更多的C-黑客的风格来短编写代码的基础上你知道怎么目标系统的存储架构的工作。正如已经说您可以正常地得到与它如果你不实际的目标很多不同的平台。我相信有些编译器可能会让你使用包装指令还(我知道他们做的结构)?
一个很好的例子2.可以发现在 变种 类广泛用于COM.
正如其他人所述,工会结合枚举和包裹成结构可用于实施标记的工会。一个实际用于实施生锈的 Result<T, E>
, ,这是最初使用实施了一个纯粹的 enum
(锈可以举行额外数据中枚举的变体)。这里是C++的例子:
template <typename T, typename E> struct Result {
public:
enum class Success : uint8_t { Ok, Err };
Result(T val) {
m_success = Success::Ok;
m_value.ok = val;
}
Result(E val) {
m_success = Success::Err;
m_value.err = val;
}
inline bool operator==(const Result& other) {
return other.m_success == this->m_success;
}
inline bool operator!=(const Result& other) {
return other.m_success != this->m_success;
}
inline T expect(const char* errorMsg) {
if (m_success == Success::Err) throw errorMsg;
else return m_value.ok;
}
inline bool is_ok() {
return m_success == Success::Ok;
}
inline bool is_err() {
return m_success == Success::Err;
}
inline const T* ok() {
if (is_ok()) return m_value.ok;
else return nullptr;
}
inline const T* err() {
if (is_err()) return m_value.err;
else return nullptr;
}
// Other methods from https://doc.rust-lang.org/std/result/enum.Result.html
private:
Success m_success;
union _val_t { T ok; E err; } m_value;
}