我给人的印象是,访问union
除上一组之外的成员是UB,但是我似乎找不到一个可靠的参考(除了声称它是UB的答案之外,但没有标准的任何支持)。
那么,这是未定义的行为吗?
我给人的印象是,访问union
除上一组之外的成员是UB,但是我似乎找不到一个可靠的参考(除了声称它是UB的答案之外,但没有标准的任何支持)。
那么,这是未定义的行为吗?
Answers:
混乱之处在于C明确允许通过联合进行类型修饰,而C ++(C ++ 11)没有此类许可。
6.5.2.3结构和工会成员
95)如果用于读取联合对象的内容的成员与上次用于在该对象中存储值的成员不同,则该值的对象表示的适当部分将在新的对象中重新解释为对象表示类型,如6.2.6中所述(有时称为“类型校正”的过程)。这可能是陷阱表示。
C ++的情况:
9.5工会[class.union]
在联合中,最多可以在任何时间激活一个非静态数据成员,即,可以随时将一个最多非静态数据成员的值存储在一个联合中。
后来C ++的语言允许使用struct
带有s且具有共同初始序列的并集。但是,这不允许进行类型调整。
为了确定在C ++中是否允许联合类型修剪,我们必须进一步搜索。回想起那个c99 是C ++ 11的规范性参考(并且C99与C11的语言类似,允许联合类型对齐):
3.9类型[basic.types]
4-类型T的对象的对象表示形式是由类型T的对象占用的N个无符号字符对象的序列,其中N等于sizeof(T)。对象的值表示形式是持有类型T值的位集合。对于普通可复制类型,值表示形式是对象表示形式中确定值的位集合,该值是实现的一个离散元素-定义的一组值。42
42)目的是C ++的存储模型与ISO / IEC 9899编程语言C的存储模型兼容。
当我们阅读时,它变得特别有趣
3.8对象生存期[basic.life]
T类型的对象的生命周期在以下时间开始:-获得类型T具有正确的对齐方式和大小的存储,并且-如果该对象具有非平凡的初始化,则其初始化完成。
因此,对于包含在并集中的基本类型(即,ipso实际上具有微不足道的初始化),对象的生存期至少包含并集本身的生存期。这使我们可以调用
3.9.2化合物类型[basic.compound]
如果类型T的对象位于地址A,则将其值为地址A的cv T *类型的指针指向该对象,而不管如何获取该值。
假设我们感兴趣的操作是类型操作,即获取一个非活动联合成员的值,并且根据以上所述,我们对该成员所引用的对象具有有效的引用,那么该操作就是左值-右值转换:
4.1左值到右值转换[conv.lval]
非功能,非数组类型的glvalue
T
可以转换为prvalue。如果T
是不完整的类型,则必须进行此转换的程序格式错误。如果glvalue所引用的对象不是类型的对象,T
也不是从派生的类型T
的对象,或者该对象未初始化,则需要进行此转换的程序将具有未定义的行为。
然后的问题是,是否通过存储到活动联合成员来初始化作为非活动联合成员的对象。据我所知,事实并非如此,尽管如此:
char
阵列存储中并返回(3.9:2),或者如果定义了非活动成员对并集的访问,并且该访问被定义为遵循对象和值表示形式,则没有上述中介之一的访问是未定义的行为。这对允许对此类程序执行的优化有影响,因为该实现当然可以假定未发生未定义的行为。
也就是说,尽管我们可以合法地为非活动的工会成员形成一个左值(这就是为什么无需构造即可分配给非活动的成员是可以的),但它仍被认为是未初始化的。
memcpy
实现(使用unsigned char
左值访问对象),不允许对*p
after的访问int *p = 0; const int *const *pp = &p;
(即使从int**
to 的隐式转换const int*const*
是有效的),甚至不允许对c
after的访问struct S s; const S &c = s;
。CWG问题616。新的措辞允许吗?还有[basic.lval]。
&
符应用于工会成员时的含义(以及C标准也需要弄清楚)。我认为结果指针至少在下一次直接使用或间接使用任何其他成员左值之前都应该可用于访问该成员,但是在gcc中,指针不能使用那么长时间,这就提出了一个问题:该&
运营商的解释是:。
C ++ 11标准这样说
9.5联盟
在联合中,最多可以在任何时间激活一个非静态数据成员,即,可以随时将一个最多非静态数据成员的值存储在一个联合中。
如果仅存储一个值,那么如何读取另一个值?它只是不在那里。
gcc文档在实现定义的行为下列出了这一点
- 使用不同类型的成员访问联合对象的成员(C90 6.3.2.3)。
对象表示形式的相关字节被视为用于访问的类型的对象。请参阅类型处理。这可能是陷阱表示。
表示这不是C标准所必需的。
2016-01-05:通过这些评论,我链接到C99缺陷报告#283,该报告在C标准文档中添加了类似的文本作为脚注:
78a)如果用于访问联合对象的内容的成员与上次用于在该对象中存储值的成员不同,则该值的对象表示的适当部分将在新版本中重新解释为对象表示。类型,如6.2.6中所述(有时称为“类型校正”的过程)。这可能是陷阱表示。
考虑到脚注不是该标准的规范,因此不确定是否可以澄清。
我认为最接近标准的说法是它的未定义行为是它定义包含共同初始序列的联合的行为(C99,第6.5.2.3/5节):
为了简化并集的使用,做出了一项特殊保证:如果并集包含几个共享共同的初始序列的结构(请参见下文),并且如果并集对象当前包含这些结构之一,则可以检查该并集。可以看到联合的完整类型的声明的任何位置的初始部分。如果相应的成员对一个或多个初始成员的序列具有兼容的类型(对于位域,则具有相同的宽度),则两个结构共享一个公共的初始序列。
C ++ 11在§9.2/ 19中给出了类似的要求/权限:
如果标准布局联合包含两个或多个共享公共初始序列的标准布局结构,并且如果标准布局联合对象当前包含这些标准布局结构之一,则可以检查任何标准布局结构的公共初始部分其中。如果相应成员具有与布局兼容的类型,并且对于一个或多个初始成员的序列,两个成员都不是位字段,或者两者都不是具有相同宽度的位字段,则两个标准布局结构共享一个公共的初始序列。
尽管都没有直接声明,但它们都带有强烈的含义,即只有在1)它是最近写入的成员的一部分或2)是公共首字母的一部分时,才 “检查”(读取)成员是“允许的”。序列。
这并不是直接声明否则将是未定义的行为,但这是我所知道的最接近的一种。
union
s的大多数拙劣用法时,我感到非常高兴,因为某个博客给我留下了这样的印象,那就是可以,并围绕它构建了多个大型结构和项目。现在我想我可能还是可以的,因为我的union
s确实包含了在前面具有相同类型的类
union
包含例如 a uint8_t
和a- class Something { uint8_t myByte; [...] };
我会认为此附加条件也将在这里适用,但措辞非常刻意仅允许struct
s。幸运的是,我已经在使用那些原始而非原始的原始内容了:O
我用一个例子很好地解释了这一点。
假设我们具有以下结合:
union A{
int x;
short y[2];
};
我很好地假设sizeof(int)
给出4,sizeof(short)
得出2。
当您编写得union A a = {10}
很好时,在其中创建一个新的类型A的变量var,其值为10。
您的记忆应如下所示:(请记住,所有工会成员都位于同一位置)
| x | | y [0] | y [1] | ----------------------------------------- a-> | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1010 | -----------------------------------------
如您所见,ax的值为10,ay 1的值为10,ay [0]的值为0。
现在,如果我这样做会怎么样?
a.y[0] = 37;
我们的记忆将如下所示:
| x | | y [0] | y [1] | ----------------------------------------- a-> | 0000 0000 | 0010 0101 | 0000 0000 | 0000 1010 | -----------------------------------------
这会将ax的值转换为2424842(十进制)。
现在,如果您的并集具有浮点数或两倍数,则由于存储精确数字的方式,您的内存映射将更加混乱。更多信息,你可以在这里。