ccastingunions

Casting at compile time


Consider the following union:

/**
 * @brief   Holding parameter 16-bit value variant representation
 */
typedef union
{
    uint16_t    u;  /*!< unsigned integer 16-bit */

    int16_t     i;  /*!< signed integer 16-bit */

} hparamValue_t;

And this function that takes such a union as an input parameter:

void hparam_set(const hparamValue_t value);

When calling this function, I have only an uint16_t or int16_t at hand. So to use it in a "clean" way, I do:

uint16_t myVar; // somewhere

// when calling the function
hparamValue_t hpv;
hpv.u = myVar;
hparam_set(hpv);

But I am more leaning into this construct:

hparam_set(*((hparamValue_t*)&myVar));

While it doesn't look too obvious, it doesn't force me to create an additional, artificial variable. The question is, would the cast to pointer and then value be unfolded still at compile time? This seems safe, anything I am missing?


Solution

  • When accessing an object using a different type than it is defined with, we have to consider the aliasing rules in C 2018 6.5 7:

    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.

    hparam_set(*((hparamValue_t*)&myVar)); conforms to this rule since hparamValue_t is a union type that includes a member with the same type as myVar. A second rule to consider is that when a pointer to one object type is converted to a pointer to another object type, the C standard does not define the behavior if the pointer being converted does not have the alignment required for the new type. In the case of a pointer to a uint16_t being converted to a pointer containing only 16-bit members, the alignment requirement of the union is the same as the alignment requirement of uint16_t in ordinary C implementations, but the C standard does not require this.

    Another way to pass a hparamValue_t to hparam_set is by using a compound literal:

    hparam_set((hparamValue_t) { .u = myVar });
    

    A compound literal has the form (type) { initializers }. The initializers have same form as initializers in a definition. A compound literal is an unnamed object of the designated type. Outside a function, it has static storage duration. Inside a function, it has automatic storage duration the same as an ordinary object defined at the same location.