leg*_*s2k 237 c c++ unions type-punning
我早先使用过工会; 今天,当我读到这篇文章并开始知道这段代码时,我感到震惊
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
Run Code Online (Sandbox Code Playgroud)
实际上是未定义的行为即从工会成员读取而不是最近编写的那个导致未定义的行为.如果这不是工会的预期用途,那是什么?有人可以详细解释一下吗?
更新:
事后我想澄清一些事情.
如果标准布局联合包含多个共享公共初始序列的标准布局结构,并且如果此标准布局联合类型的对象包含其中一个标准布局结构,则允许检查任何标准布局结构的公共初始序列.标准布局结构成员.§9.2/ 19:如果相应的成员具有布局兼容类型且两个成员都不是位字段,或者两者都是具有相同宽度的位字段,则一个或多个初始序列的两个标准布局结构共享一个公共初始序列成员.
C89/90在未指明的行为(附件J)中称之为,而K&R的书称其实施已定义.来自K&R的报价:
这是联合的目的 - 一个可以合法地保存几种类型中的任何一种的变量.[...]只要用法一致:检索到的类型必须是最近存储的类型.程序员有责任跟踪当前存储在联合中的类型; 如果将某些内容存储为一种类型并将其提取为另一种类型,则结果将依赖于实现.
从Stroustrup的TC++ PL中提取(强调我的)
使用工会对于数据的兼容性至关重要[...] 有时会被误用于"类型转换 ".
最重要的是,这个问题(自我提出以来其标题保持不变)的目的是为了理解工会的目的而不是标准允许的内容例如,当然,C++标准允许使用继承进行代码重用.将继承作为C++语言特性引入并不是目的或初衷.这就是安德烈的答案继续作为公认的答案的原因.
AnT*_*AnT 371
工会的目的是相当明显的,但由于某些原因,人们经常会错过它.
union的目的是通过使用相同的内存区域在不同时间存储不同的对象来节省内存.而已.
它就像一个酒店的房间.不同的人生活在不重叠的时期.这些人永远不会见面,而且通常对彼此一无所知.通过妥善管理房间的分时(即确保不同的人不同时分配到一个房间),一个相对较小的酒店可以为相对较多的人提供住宿,这就是酒店是给.
这正是联盟所做的.如果您知道程序中的多个对象包含具有非重叠值生命周期的值,那么您可以将这些对象"合并"到一个联合中,从而节省内存.就像酒店房间在每个时刻最多只有一个"活跃"租户一样,工会在每个节目时刻最多只有一个"活跃"成员.只能读取"活动"成员.通过写入其他成员,您可以将"活动"状态切换到该其他成员.
由于某种原因,联盟的这个原始目的得到了"覆盖"与完全不同的东西:写一个联盟的一个成员,然后通过另一个成员检查它.这种记忆重新解释(又名"打字")不是对工会的有效使用.它通常导致未定义的行为被描述为在C89/90中产生实现定义的行为.
编辑:使用工会进行打字(即写一个成员然后再读另一个成员)在C99标准的技术勘误表中给出了更详细的定义(参见DR#257和DR#283).但请记住,正确地说,这并不能通过尝试读取陷阱表示来防止您遇到未定义的行为.
Eri*_*ler 36
您可以使用联合来创建如下所示的结构,其中包含一个字段,告诉我们实际使用了union的哪个组件:
struct VAROBJECT
{
enum o_t { Int, Double, String } objectType;
union
{
int intValue;
double dblValue;
char *strValue;
} value;
} object;
Run Code Online (Sandbox Code Playgroud)
Dav*_*eas 34
从语言的角度来看,行为是未定义的.考虑到不同的平台在内存对齐和字节序中可能有不同的约束.big endian与little endian机器中的代码将以不同方式更新结构中的值.修复语言中的行为将要求所有实现使用相同的字节顺序(和内存对齐约束...)来限制使用.
如果你正在使用C++(你使用的是两个标签)并且你真的关心可移植性,那么你可以使用结构并提供一个setter,它uint32_t通过位掩码操作来获取和设置字段.使用函数可以在C中完成相同的操作.
编辑:我期待AProgrammer写下一个投票答案并关闭这个.正如一些评论所指出的那样,通过让每个实现决定做什么来对标准的其他部分进行字节序处理,并且对齐和填充也可以以不同方式处理.现在,AProgrammer隐含引用的严格别名规则是重要的一点.允许编译器对变量的修改(或缺少修改)做出假设.在联合的情况下,编译器可以重新排序指令并将每个颜色分量的读取移动到写入颜色变量上.
bob*_*obo 18
我经常遇到的最常见的用法union是别名.
考虑以下:
union Vector3f
{
struct{ float x,y,z ; } ;
float elts[3];
}
Run Code Online (Sandbox Code Playgroud)
这是做什么的?它允许Vector3f vec;通过任何名称干净,整洁地访问成员:
vec.x=vec.y=vec.z=1.f ;
Run Code Online (Sandbox Code Playgroud)
或者通过整数访问数组
for( int i = 0 ; i < 3 ; i++ )
vec.elts[i]=1.f;
Run Code Online (Sandbox Code Playgroud)
在某些情况下,通过名称访问是您可以做的最清楚的事情.在其他情况下,特别是当以编程方式选择轴时,更容易做的是通过数字索引访问轴 - 0表示x,1表示y,2表示z.
小智 9
正如你所说,这是严格未定义的行为,尽管它将在许多平台上"起作用".使用联合的真正原因是创建变体记录.
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;
Run Code Online (Sandbox Code Playgroud)
当然,您还需要某种鉴别器来说明变体实际包含的内容.请注意,在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;
Run Code Online (Sandbox Code Playgroud)
在litlle内存时,这个结构使用的内存少于拥有所有成员的结构.
顺便提一下C
typedef struct {
unsigned int mantissa_low:32; //mantissa
unsigned int mantissa_high:20;
unsigned int exponent:11; //exponent
unsigned int sign:1;
} realVal;
Run Code Online (Sandbox Code Playgroud)
访问位值.
尽管这是完全未定义的行为,但实际上,它将与几乎所有编译器一起使用。这种范例被广泛使用,以至于任何自重的编译器在这种情况下都需要做“正确的事”。它肯定比类型处理优先,后者可能会在某些编译器中生成残破的代码。
在C++中,Boost Variant实现了union的安全版本,旨在尽可能地防止未定义的行为.
它的性能与enum + union构造相同(堆栈分配太多等),但它使用类型的模板列表而不是enum:)