Search code examples
cunionatomiccompare-and-swap

Are _Atomic members of unions a good idea?


I'm using _Atomic integers inside of unions, such as:

union dedup_option_seq {
    struct {
        uint32_t seq       :18;     // enough bits for seq
        uint32_t not_empty :1;      // inverted so that zero is empty
    };
    _Atomic uint32_t value;
};

I can then happily use &foo.value as a parameter in the atomic_...() functions.

I can set unions' values using the syntax:

union dedup_option_seq foo = {
    .seq = 42,
    .not_empty = 1
};

It's all working nicely, but am I causing any undefined behaviour, or other issues, when using unions with an _Atomic member?


Solution

  • It's all working nicely, but am I causing any undefined behaviour, or other issues, when using unions with an _Atomic member?

    C allows you to read back a different member of a union than the one that was last set. This much is ok in C (but not in C++). C does not restrict that based on the qualifiers (such as _Atomic) of the types involved, so that is not directly a problem.

    However,

    • _Atomic types are not guaranteed to have the same size or representation as their corresponding non-_Atomic counterparts, and

    • the layout of bitfields within a struct is much less constrained than you probably think it is.

    Between these, C allows of your example that

    • The representation of value might not overlap the entirety of the representations of seq and not_empty. In an implementation where it does not, your attempts to manipulate those bits atomically via value would not have the effect you expect.

    • Even if value does overlap the entirety of seq and not_empty, some bits of the latter two might not correspond to value bits of the former. In that case, your attempts to manipulate those bits atomically via value might not have the effect you expect.

    • Assigning to seq and / or not_empty might cause value to contain a trap representation, even though ordinary uint32_t does not have any trap representations. In an implementation where that can be the case, attempting to manipulate those bits atomically via value could cause a trap, possibly crashing the program. Whether and when this happens could be data dependent.

    Additionally, manipulating _Atomic object value non-atomically via other members of the union seems fraught. At minimum, such manipulations do not enjoy any of the atomicity guarantees that manipulating value directly provides.

    So is there undefined behavior here? Hard to say. There are definitely questions about the behavior that the language spec does not directly address. Some or all of that might be better characterized as unspecified than as undefined, but the former is not that much better than the latter if you require your program to reproducibly yield the same results on diverse C implementations.