如果联合体的活动和非活动成员是标准布局类型,例如像int
这样的基元类型,则使用它们是法律的的。
另一方面,它是UB到const_cast
-离开一个简单变量的volatile
并使用该变量。
使用此联合的两个成员是否法律的(无UB)?
union VU {
int nv;
volatile int v;
};
应该更正式一点
union VU {
struct {
int v;
} nv;
struct {
volatile int v;
} v;
};
2条答案
按热度按时间s4n0splo1#
如果“使用此联合的两个成员”是指试图利用公共初始序列规则通过非
volatile
glvalue访问volatile
对象:答案是否定的,这是不法律的的。[class.mem.general]/26实际上对此非常清楚:
在具有结构类型
T1
的活动成员的标准布局联合中,如果m
是T1
和T2
的公共初始序列的一部分,则允许读取结构类型T2
的另一个联合成员的非静态数据成员m
;行为就好像T1
的对应成员被提名一样。[* 示例5*:...] [* 注10*:通过非volatile类型的glvalue阅读volatile对象具有未定义的行为([dcl.type.cv])。- end note]在上面的例子中,阅读
x.nv.v
的行为就好像指定了实际活动成员的对应成员,* 即 *,它读取活动成员x.v
的成员x.v.v
。由于这是通过非易失性glvalue读取易失性对象x.v.v.
,所以该行为是未定义的。另一方面,如果你用相反的方法(激活
x.nv.v
,然后通过x.v.v
读取它),那么它将是法律的的;这和阅读const_cast<volatile int&>(x.nv.v)
没有什么区别。arknldoa2#
C99和以后的标准有意放弃对某些情况的管辖权,否则它将基于实现定义的特性为一些而不是所有实现明确定义行为。
例如,在C89中,
-16384<<1
的行为被明确定义(并且有用地)在int
和unsigned
都没有任何填充位或陷阱表示的二进制补码平台上,但是在与0x8000u
相关联的位模式如果被解释为int
则将是陷阱表示的实现上,它将产生未定义的行为。因为可能存在动作可以调用UB的实现,所以C99将所有实现上的这种转换重新归类为“未定义行为”。volatile
限定的对象的语义是实现定义的。大多数实现定义它们的语义的方式是,将volatile
限定符应用于不“需要”限定符的对象,将产生与缺少限定符的对象兼容的语义(在定义限定符的情况下)。然而,因为有可能,一种实现可以以与非限定对象完全不同的方式存储volatile
限定对象,并且使用易失性限定指针中的比特来指示目标将需要哪种访问,使用非限定左值来访问volatile限定的对象可能会产生不可预知的效果,无论行为是否在缺少限定符的情况下定义。因此,标准放弃了对实现如何处理代码(如您的代码)的管辖权。这种放弃并不意味着暗示volatile限定符的已定义行为将明确指定此类构造的行为的实现不应该以与之一致的方式行为。尽管如此,因为一些编译器特意将这种管辖权的放弃解释为邀请无意义地处理这种构造,这样的代码应当被认为仅适合于与避免这样的处理的实现一起使用。