lp0 on fire

My personal blog

C89-OOP: Casting

In most OOP languages it's possible to cast base <-> derived. A quick example of what I mean:

Bar bar = new Bar();
Foo a = (Foo)bar;
Bar b = (Bar)a;

In C89, the same is possible through pointer magic:

Bar bar;
Foo* a;
Bar b;

Bar_ctor(&bar, 1, 2);
a = (Foo*)&bar);
b = (*(Bar*)&a);

Remember when I said it was important to have the base member first? The trick from above only works because of how memory alignment and padding rules work for structs;

"A pointer to a structure object, suitably converted, points to its initial member. There may be unnamed padding within a structure object, but not at its beginning" -- WG14/N1256 Section 6.7.2.1.13

This means that alignment is kept for the first member of a struct, which in return allows us to cast. Meaning it will cast correctly Bar -> Foo -> Bar (tested on Clang 7+ and GCC 4+).

Sometimes it might works to cast a second or later member to the desired type and back, but this is not garuanteed to work by the standard. So far with Clang 14 I was able to do this on the stack but got segfaults on the heap.

Side effects

When downcasting, ensure that the class you're downcasting from is indeed of this type, otherwise you'll get random values from memory addresses and possibly a segfault.

When downcasting from an upcast using pointer reference cast and then accessing a pointer member will also result into a segfault.

typedef struct A A;
typedef struct B B;

struct A
{
    int val;
};

struct B
{
    A base_a;
    A* virt_a;
};

void B_ctor(B* self, int a)
{
    self->base_a.val = a;
    self->virt_a = &self->baseA;
}

int main()
{
    B b;
    A a;

    B_ctor(&b, 1);
    a = (*(A*)&b);
    b = (*(B*)&a);

    return 0;
}

For example, accessing b.virt_a->val would result in a segfault, but accessing b.base_a->val would not.

Conclusion

You now know how to upcast and downcast structs in C89, as well as the possible side effects from doing this. Personally I would try to be explicit where possible and not make these kind of casts. In the next article I'll discuss inheritance through interfaces.