Consider:
typedef union
{
int intval;
const int const_intval;
}myUnion_t;
int main(void)
{
myUnion_t x = {0};
x.intval = 5;
printf("intval = %d\n", x.intval);
printf("const_intval = %d\n", x.const_intval);
}
myUnion_t
UB itself when defined as it is here?intval
UB?None of the compilers tested emit warning (compiled with C++ compilers too)
The dupe is only related to question 2
, not the main one.
- Is
myUnion_t
UB itself when defined as it is here?
No.
The union specifier provided in the question conforms to C grammar as described in C23 6.7.3.2/1
There is no constraint against members being const
qualified or there being a mixture of const
and non-const
members (C23 6.7.3.2/2-6). The constraints are where we would find limitations placed on top of the grammar, such as on the usage of const
qualification in particular where qualified types are permitted in general.
C explicitly allows that
A member of a structure or union can have any complete object type other than a variably modified type.
(C23 6.7.3.2/11)
const
being a type qualifier, it is part of the types of any so-qualified members. That is, const int
is a different type than int
, and is among the "any complete type[s]" that are allowed for union members.
The only thing to quibble about is the type name ending in _t
. C has nothing in particular to say about that, but if POSIX matters to you, then you should be aware that it reserves names of that form.
- Is any assignment of
intval
UB?
An answer to a similar question says say so, relying on the C11 version of this C23 provision in paragraph 6.4.7.1/7:
If an attempt is made to modify an object defined with a
const
-qualified type through use of an lvalue with non-const
-qualified type, the behavior is undefined.
I tend to agree, but I don't think that application of that provision is entirely clear cut. Unions consist of logically distinct members with overlapping storage (C23 6.2.5/25), and, notionally, a union contains a value for only one member at a time (C23 note 38). There is a question, then, of whether assigning to x.intval
should be interpreted as modifying x.const_intval
. At what level(s) should the above provision be applied? Indeed, the answer might be different for C++, which makes a stronger distinction between union members by disclaiming defined behavior for union-based type punning -- but there, even if the assignment had defined behavior, the second printf()
call, in which x.const_intval
is accessed, would not.
A different path would go through the specifications for assignment operations -- specifically that assignment operators must have modifiable lvalues as their left operands (C23 6.5.17.1/2). Then, per C23 6.3.3.1/1,
A modifiable lvalue is an lvalue that [...] if it is a structure or union, does not have any member [...] with a const-qualified type.
We also have that when a member is accessed via a host structure or union by use of the .
or ->
operator, the type of the resulting lvalue inherits the qualifiers of the lvalue designating the host structure or union. That means that if x
were declared const
then assigning to x.intval
would have undefined behavior. But that doesn't quite get us to the expected conclusion either, because x.intval
inherits qualifiers from the type of x
, not modifiability directly. x
itself is not a modifiable lvalue, but x.intval
seems to satisfy the spec's definition of "modifiable lvalue".
I think that's a flaw in the spec.
The safest interpretation is that the assignment to x.intval
produces UB. Doubly so if x.const_intval
is subsequently accessed, and triply so if x.const_intval
had been initialized. As a practical matter, even if you prefer to interpret the assignment to x.intval
as having defined behavior, there is a significant risk that your C implementation does not, such that the assignment elicits unexpected, unwanted behavior from your program in practice.