Search code examples
cinheritancelanguage-lawyerflexible-array-memberpointer-conversion

Is it legal to implement inheritance in C by casting pointers between one struct that is a subset of another rather than first member?


Now I know I can implement inheritance by casting the pointer to a struct to the type of the first member of this struct.

However, purely as a learning experience, I started wondering whether it is possible to implement inheritance in a slightly different way.

Is this code legal?

#include <stdio.h>
#include <stdlib.h>

struct base
{
    double some;
    char space_for_subclasses[];
};

struct derived
{
    double some;
    int value;
};

int main(void) {
    struct base *b = malloc(sizeof(struct derived));
    b->some = 123.456;
    struct derived *d = (struct derived*)(b);
    d->value = 4;
    struct base *bb = (struct base*)(d);
    printf("%f\t%f\t%d\n", d->some, bb->some, d->value);
    return 0;
}

This code seems to produce desired results , but as we know this is far from proving it is not UB.

The reason I suspect that such a code might be legal is that I can not see any alignment issues that could arise here. But of course this is far from knowing no such issues arise and even if there are indeed no alignment issues the code might still be UB for any other reason.


Solution

  • This is more-or-less the same poor man's inheritance used by struct sockaddr, and it is not reliable with the current generation of compilers. The easiest way to demonstrate a problem is like this:

    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    struct base
    {
        double some;
        char space_for_subclasses[];
    };
    struct derived
    {
        double some;
        int value;
    };
    
    double test(struct base *a, struct derived *b)
    {
        a->some = 1.0;
        b->some = 2.0;
        return a->some;
    }
    
    int main(void)
    {
        void *block = malloc(sizeof(struct derived));
        if (!block) {
            perror("malloc");
            return 1;
        }
        double x = test(block, block);
        printf("x=%g some=%g\n", x, *(double *)block);
        return 0;
    }
    

    If a->some and b->some were allowed by the letter of the standard to be the same object, this program would be required to print x=2.0 some=2.0, but with some compilers and under some conditions (it won't happen at all optimization levels, and you may have to move test to its own file) it will print x=1.0 some=2.0 instead.

    Whether the letter of the standard does allow a->some and b->some to be the same object is disputed. See http://blog.regehr.org/archives/1466 and the paper it links to.