Home > database >  Upcasting and downcasting inherited structures in C
Upcasting and downcasting inherited structures in C

Time:12-29

I'm wondering if presented example is safe(no UB) in C:

typedef struct {
    uint32_t a;
} parent_t;

typedef struct {
    parent_t parent;
    uint32_t b;
} child_t;

typedef struct {
    uint32_t x;
} unrelated_t;

void test_function(parent_t* pParent) {
    ((child_t*)pParent)->b = 5U; // downcast is valid only if relation chain is valid
}

int main()
{
    child_t child;
    unrelated_t ub;
    test_function((parent_t*)&child); // valid upcast?
    test_function((parent_t*)&ub); // probably UB?

    return 0;
}

Due to an explicit cast there is no warranty and good typechecking but as long as proper parameters are passed this should work right?

CodePudding user response:

The rule for casting is https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7 :

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.

Also in case of structures https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p15 :

A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa.

And then accessing https://port70.net/~nsz/c/c11/n1570.html#6.5p7 :

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)

  • 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.

as long as proper parameters are passed this should work right?

There is a vast ocean between "should work" and "is guaranteed to work". The goal is to write code that is guaranteed to work as intended – it has no undefined behavior, because there is no guarantee what happens when it's undefined.

test_function((parent_t*)&child); // valid upcast?

Yes, it's defined behavior. &child points to &child.parent, so the resulting pointer has to be properly aligned to parent_t.

Then (child_t*)pParent is also valid, because it points to a child_t object.

Then accessing it ((child_t*)pParent)-> is also valid, because there is a child_t object accessed with the same type child_t as it is, so it's a compatible type for sure.

test_function((parent_t*)&ub); // probably UB?

Most probably, casting a pointer from unrelated_t* to parent_t* is completely fine – most probably alingof(unrelated_t) is equal to alingof(parent_t), because there are only uint32_t inside. But, it depends. It may be not valid, when the resulting pointer of (parent_t*)&ub is not properly aligned to parent_t.

After that, a similar story with (child_t*)pParent with the same pointer value inside test_function. Note that child_t may have stricter alignment requirements from parent_t, so while the previous cast may be valid, this one may be not, in the same fashion – depends on alignment requirements of types involved.

Then after that, accessing the value with ((child_t*)pParent)-> is undefined behavior. Accessing a pointer to unrelated_t object via child_t * handle is undefined behavior. child_t is not a compatible type with unrelated_t.

  • Related