Search code examples
cstrict-aliasingflexible-array-member

flexible array in C and dereferencing type-punned pointer error


When I try to compile the code below with gcc -O3 -Wall -Werror -std=c99 main.c I get an error like "dereferencing type-punned pointer will break strict-aliasing rules" at #3, but not in #2 or #1. I can dereference type-punned "char *", but why can't I do the same with flexible arrays?

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

struct Base {
        void (*release) (struct Base *);
        size_t sz;

        char *str_foo;
        char rest[];
};

struct Concrete {
        char *str_bar;
};

void
Base_release(struct Base *base)
{
        free(base);
}

struct Base *
Base_new(size_t sz_extra)
{
        size_t sz = sizeof(struct Base) + sz_extra;
        struct Base *base = (struct Base *)malloc(sz);
        base->release = &Base_release;
        base->sz = sz;

        base->str_foo = "foo";
        return base;
}

#define BASE_FREE(_obj) (_obj)->release(_obj)
#define BASE_CAST(_type, _obj) ((struct _type *)((_obj)->rest))
#define BASE_CAST_2(_type, _obj) ((struct _type *)((char *)(_obj)+sizeof(struct Base)))

struct Base *
Concrete_new()
{
        struct Base *base = Base_new(sizeof(struct Concrete));
        struct Concrete *concrete = BASE_CAST(Concrete, base);
        concrete->str_bar = "bar";
        return base;
}

int main(int argc, const char *argv[])
{
        struct Base *instance = Concrete_new();
        printf("Base str: %s\n", instance->str_foo);

        // #1 - Legal
        struct Concrete *cinstance = BASE_CAST(Concrete, instance);
        printf("#1: Concrete str: %s\n", cinstance->str_bar);

        // #2 - Legal
        printf("#2: Concrete str: %s\n", BASE_CAST_2(Concrete, instance)->str_bar);

        // #3 - Compile error
        printf("#3: Concrete str: %s\n", BASE_CAST(Concrete, instance)->str_bar);

        BASE_FREE(instance);

        return 0;
}

EDIT 1: There is more concrete example showing problem below:

struct s {                               
        char a;                              
};                                          
char *const a = malloc(sizeof(struct s));
char b[sizeof(struct s)];   
((struct s *)((char *)a))->a = 5; // This is a valid case
((struct s *)(a))->a = 5; // OK
((struct s *)((char *)b))->a = 5; // ???

Solution

  • They all break strict aliasing by aliasing an array of char as a type that is not char. (The reverse of that is permitted though).

    Also they all may have alignment problems; rest might not be correctly aligned for a struct.

    You might not get a warning because:

    • The compiler's detection of aliasing violation is not that good, and/or
    • Your system actually permits this aliasing even though it is non-portable

    If you replaced rest with a pointer to dynamically allocated memory then both of those problems go away, since the "effective type" of dynamically allocated memory is determined by what you store in it.

    Note, it seems to me a much better solution to do:

    struct Concrete
    {
        struct Base base;
        char const *str_bar;
    };
    

    or alternatively, keep Concrete as you have it and do:

    struct BaseConcrete
    {
        struct Base base;
        struct Concrete concrete;
    };