Is accessing a member of an unaligned union undefined behavior even if the member being accessed is sufficiently aligned?

104 Views Asked by At

In C bad things can happen if I try to access a type through a pointer that is not aligned:

int x[2]; // Assuming CHAR_BIT == 8 && sizeof(int) == 4
*(int *)((char *)x+1) = 10; /* Undefined behavior due to unaligned pointer dereference
    even though there are no out of bounds accesses */

However what if the type in question is a union but I am only accessing a member of the union that does not have the alignment requirements?

union X {
    int i;
    char c[4];
} x[2];
((union X *)((char *)x+1))->i = 10; /* I am guessing this is still undefined behavior
    because accessing an unaligned int through a union does not make a difference as far as I know */

But would the following invoke undefined behavior?

((union X *)((char *)x+1))->c[0] = 10;

I am in doubt because on one hand I am dereferencing a union pointer via the -> operator and the union has alignment requirements because of the int field, but on the other hand, I am not touching the int field. So does that result in any unaligned pointer access violations?

2

There are 2 best solutions below

4
Barmar On BEST ANSWER

The cast causes undefined behavior. From C23 6.3.2.3(7)

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

Since (union X *)((char *)x+1)) is not correctly aligned for the union, the second sentence applies. It doesn't matter whether you actually dereference it.

6
supercat On

Clang will sometimes generate nonsensical code in situations where a pointer to a union type receives an address which is not aligned for all of the members thereof, even if code makes no use of any members whose alignment requirements are not satisfied. When targeting the Cortex-M0 (which does not support unaligned word accesses) for example, the function:

union fnord { unsigned char b[4]; unsigned w; };
void test(union fnord *restrict p1, union fnord *restrict p2)
{
    p1->b[0] = p2->b[0];
    p1->b[1] = p2->b[1];
    p1->b[2] = p2->b[2];
    p1->b[3] = p2->b[3];
}

will be processed as a single word load and store. I don't think the authors of the Standard to make it impossible to have a type which could be used to access objects with minimal alignment requirements, but presumed capable of aliasing objects with coarser requirements, but the way clang interprets it does.