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; // ???
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:
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;
};