ProgDevel
ProgDevel

Reputation: 77

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

Upvotes: 4

Views: 272

Answers (2)

M.M
M.M

Reputation: 141618

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;
};

Upvotes: 1

keltar
keltar

Reputation: 18399

First of all, it isn't related with flexible arrays but rather with any array. You could use fixed-size-big-enough array and see the same results.

Most obvious workaround is your BASE_CAST_2. Another one could be using offsetof instead of sizeof, especially in cases where structures tail is not flexible.

Difference between first and third cases is tricky. They're both breaking strict aliasing rules, but gcc sometimes allows that, when it can't determine origin of lvalue. However, with -Wstrict-alising=2 it will give warning in both cases. I'm not sure it is guaranteed to generate valid code even if no warning issued with standard -Wstrict-aliasing (but in that given example it does).

Second case looks ok because casting structure* to char* is allowed. There is however a difference between char* and char[] types.

Upvotes: 2

Related Questions