Let's say I have following piece of code, defining a structure and a union:
struct Foo
{
int i;
float f;
};
union Bar
{
struct Foo foo;
char buf[10];
};
Is it safe to assign pointer to object of type struct Foo
in place of pointer to type union Bar
and subsequently use it as such:
struct Foo fooVar;
union Bar* barPtrVar = (union Bar*) &fooVar;
bar->foo.i = 1000;
bar->foo.f = 0.5f;
as long as we are sure that only the foo
member of the union Bar
is used?
I remember that in C, pointer to the struct is the same as pointer to it's first member and as such they can be sometimes interchangeable, so though similar logic might apply here.
CodePudding user response:
This usage is safe, even if you read from the buf
member (up to a point).
Section 6.5p7 of the C standard gives the following aliasing rules:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- a type compatible with the effective type of the object,
- a qualified version of a type compatible with the effective type of the object,
- a type that is the signed or unsigned type corresponding to the effective type of the object,
- a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
- a character type.
Your use case falls under the second-to-last bullet point, and reading from the buf
member up to buf[sizeof(struct Foo)-1]
is allowed as per the last bullet point.
CodePudding user response:
In general, it is not safe. A union type will have an alignment size at least as large as the maximum alignment size of its members. Thus, a union Bar
containing a member of type struct Foo
could have stricter alignment than a struct Foo
. (This is unlikely in OP's example since the other member of union Bar
has an alignment size of 1, but it is true in general.)
Consider the code sequence:
struct Foo fooVar;
union Bar* barPtrVar = (union Bar*) &fooVar;
If union Bar
has stricter alignment than struct Foo
, then this conversion will result in undefined behavior, as per C11 6.3.2.3/7 (emphasis mine):
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned68) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.
The expression barPtrVar->foo
(of type struct Foo
) is problematic because the identifier following the ->
designates a member of a structure or union object, but barPtrVar
is not actually pointing to a union Bar
so barPtrVar->foo
is not actually a member of a structure or union object, as per C11 6.5.2.3/4 (emphasis mine):
A postfix expression followed by the
->
operator and an identifier designates a member of a structure or union object. The value is that of the named member of the object to which the first expression points, and is an lvalue.96) If the first expression is a pointer to a qualified type, the result has the so-qualified version of the type of the designated member.
Additionally, storing a value in barPtrVar->foo
(of type struct Foo
) may also modify bytes of the union Bar
that are not part of the foo
member, as per C11 6.2.6.1/7:
When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.
Since barPtrVar
is not pointing to a union Bar
, but is pointing to a smaller struct Foo
, bytes outside the bounds of the supposed member foo
(of type struct Foo
) may take unspecified values, resulting in undefined behavior because those bytes are not actually part of a union Bar
.