Union with volatile and non-volatile standard layout types

huangapple go评论69阅读模式
英文:

Union with volatile and non-volatile standard layout types

问题

可以使用工会的活跃成员和非活跃成员,如果它们是标准布局类型,例如原始类型int

另一方面,将简单变量的volatile去除掉,并使用该变量是未定义行为(UB)。

在这个联合体中,使用这两个成员是否合法(没有UB)?

union VU {
    int nv;
    volatile int v;
};

更正式地说,应该是:

union VU {
    struct {
        int v;
    } nv;
    struct {
        volatile int v;
    } v;
};
英文:

It is legal to use active and non-active members of a union if they are standard layout types e.g. like primitive types as int.

On the other hand it is UB to const_cast-away the volatile of a simple variable and use that variable.

Is it legal (no UB) to use both members of this union?

union VU {
    int nv;
    volatile int v;
};

More formal this should be

union VU {
    struct {
        int v;
    } nv;
    struct {
        volatile int v;
    } v;
};

答案1

得分: 1

如果你指的是尝试通过非volatile的glvalue来访问volatile对象,利用共同的初始序列规则,那么答案是否定的。[class.mem.general]/26 对此有明确规定:

> 在标准布局联合体中,如果T1是具有活跃成员T1的结构体类型,允许读取另一个具有结构体类型T2的联合体成员m的非静态数据成员m,前提是mT1T2的共同初始序列的一部分;其行为就像T1的相应成员被提名一样。[示例5:...] [注释10:通过非volatile类型的glvalue读取volatile对象具有未定义行为([dcl.type.cv])。— 结束注释]

在上面的例子中,读取x.nv.v的行为就像提名了实际活跃成员的相应成员一样,也就是说,它读取活跃成员x.v的成员x.v.v。由于这是通过非volatile的glvalue读取volatile对象x.v.v,其行为是未定义的。

另一方面,如果你反过来操作(使x.nv.v成为活跃成员,然后通过x.v.v读取它),那么这是合法的;这与通过const_cast<volatile int&>(x.nv.v)读取没有任何区别。

英文:

If by "use both members of this union" you mean attempting to exploit the common initial sequence rules to access a volatile object through a non-volatile glvalue:

union VU {
    struct {
        int v;
    } nv;
    struct {
        volatile int v;
    } v;
};
VU x;
x.v.v = 42;
std::cout &lt;&lt; x.nv.v;

the answer is no, it's not legal. [class.mem.general]/26 is actually very clear about this:

> In a standard-layout union with an active member of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated. [Example 5: ... ] [Note 10: Reading a volatile object through a glvalue of non-volatile type has undefined behavior ([dcl.type.cv]). &mdash; end note]

In the above example, the behaviour of reading x.nv.v is as if the corresponding member of the actually active member were nominated, i.e., it reads the member x.v.v of the active member x.v. Since this is a read of the volatile object x.v.v. through a non-volatile glvalue, the behavior is undefined.

On the other hand if you were to do it the other way around (make x.nv.v active, then read it through x.v.v) then it would be legal; it wouldn't be any different from reading through const_cast&lt;volatile int&amp;&gt;(x.nv.v).

答案2

得分: 0

C99及以后的标准特意放弃了在某些实现中明确定义行为但在其他实现中没有明确定义行为的情况下规定行为的权利,这是基于实现定义特性的。

例如,在C89中,-16384<<1的行为在补码平台上被明确定义(并且有用),其中intunsigned都没有填充位或陷阱表示时,但在一个实现中,如果将0x8000u的位模式解释为int,它将导致未定义行为。因为可能存在在这种操作可能导致未定义行为的实现,C99将所有实现上的这种移位重新分类为“未定义行为”。

volatile限定对象的语义是由实现定义的。大多数实现以一种方式定义它们的语义,即对一个本不“需要”volatile限定的对象应用volatile限定符会产生与没有该限定符时相容的语义,如果后者被定义的话。然而,可能的情况是,一个实现可能以与非限定对象完全不同的方式存储volatile限定对象,并使用volatile限定指针中的一个位来指示需要哪种类型的访问。因此,使用非限定左值来访问volatile限定对象可能会导致不可预测的影响,无论在限定符的缺席下是否定义了行为。

因此,标准放弃了实现处理类似你的代码的方式的权限。这种放弃并不意味着在定义了volatile限定符的行为会明确定义此类结构的行为的实现中,不应该以与之一致的方式运行。尽管如此,由于一些编译器竭力将这种放弃的权限解释为处理这种结构的邀请,这样的代码应该被视为只能在那些避免这种处理的实现中使用。

英文:

The C99 and later Standards deliberately waive jurisdiction over situations where it would otherwise unambiguously define behavior for some but not all implementations, based upon Implementation-Defined traits.

For example, under C89 the behavior of -16384&lt;&lt;1 was defined unambiguously (and usefully) on two's-complement platforms where neither int nor unsigned had any padding bits or trap representations, but it would have yielded Undefined Behavior on an implementation where the bit pattern associated with 0x8000u would be a trap representation if interpreted as int. Because there may exist implementations where the action could invoke UB, the C99 reclassified such shifts on all implementations under the catch-all term "Undefined Behavior".

The semantics of volatile-qualified objects are Implementation Defined. Most implementations define their semantics in such a way that applying a volatile qualifier to an object that wouldn't "need" one would yield semantics compatible with those where the qualifier was absent, in cases where the latter would be defined. Because it is possible, however, that an implementation might store volatile-qualified objects in a manner completely different from non-qualified objects, and use a bit in a volatile-qualified pointer to indicate which kind of access would be required for the target, it would in turn be possible that using a non-qualified lvalue to access a volatile-qualified object might yield unpredictable effects whether or not behavior would have been defined in the qualifier's absence.

Consequently, the Standard waives jurisdiction over how implementations process code such as yours. Such waiver is not meant to imply that implementations where the defined behavior of volatile qualifier would unambiguously specify the behavior of such constructs shouldn't behave in a manner consistent with that. Nonetheless, because some compilers go out of their way to interpret such waivers of jurisdiction as an invitation to process such constructs nonsensically, such code should be recognized as being suitable for use only with implementations that refrain from such treatment.

huangapple
  • 本文由 发表于 2023年2月10日 13:29:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75407294.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定