гибкий массив в C и разыменование ошибки указателя типа

Когда я пытаюсь скомпилировать приведенный ниже код с помощью gcc -O3 -Wall -Werror -std=c99 main.c, я получаю сообщение об ошибке типа "разыменование указателя с каламбуром типа нарушит строгие правила сглаживания" в #3, но не в #2 или #1. Я могу разыменовать символ "char *", но почему я не могу сделать то же самое с гибкими массивами?

#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: Ниже приведен более конкретный пример проблемы:

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

person ProgDevel    schedule 30.07.2014    source источник
comment
Предупреждение вынесено за нарушение правил. Вас интересует, почему ваши первые два случая не вызвали предупреждения, или вы ищете обходной путь?   -  person keltar    schedule 30.07.2014
comment
Мне интересно, почему первый случай не вызвал предупреждение. Я знаю два обходных пути: союзы и наследование по композиции. (Извините, мой английский не очень хорош :)   -  person ProgDevel    schedule 30.07.2014


Ответы (2)


Все они нарушают строгое использование псевдонимов, присваивая массиву char тип, отличный от char. (Хотя допускается и обратное).

Также у всех могут быть проблемы с выравниванием; rest может быть неправильно выровнено для структуры.

Вы можете не получить предупреждение, потому что:

  • Обнаружение компилятором нарушения псевдонимов не так хорошо, и/или
  • Ваша система на самом деле разрешает этот псевдоним, даже если он не является переносимым.

Если вы замените rest указателем на динамически выделяемую память, то обе эти проблемы исчезнут, поскольку «эффективный тип» динамически выделяемой памяти определяется тем, что вы в ней храните.

Обратите внимание, мне кажется гораздо лучшим решением:

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

или, в качестве альтернативы, оставьте Concrete как есть и сделайте:

struct BaseConcrete
{
    struct Base base;
    struct Concrete concrete;
};
person M.M    schedule 30.07.2014
comment
Спасибо за заметки о выравнивании структур и за char const *str_bar =) - person ProgDevel; 31.07.2014

Прежде всего, это связано не с гибкими массивами, а с любым массивом. Вы можете использовать массив фиксированного размера и большого размера и увидеть те же результаты.

Самый очевидный обходной путь — ваш BASE_CAST_2. Другой может использовать offsetof вместо sizeof, особенно в тех случаях, когда хвост структуры не является гибким.

Разница между первым и третьим случаями сложна. Они оба нарушают строгие правила псевдонимов, но gcc иногда допускает это, когда не может определить происхождение lvalue. Однако с -Wstrict-alising=2 это выдаст предупреждение в обоих случаях. Я не уверен, что гарантированно будет генерироваться действительный код, даже если не будет выдано предупреждение со стандартным -Wstrict-aliasing (но в этом примере это так).

Второй случай выглядит нормально, потому что разрешено преобразование structure* в char*. Однако между типами char* и char[] есть разница.

person keltar    schedule 30.07.2014
comment
Спасибо за ответ. Вы говорите, что второй случай выглядит нормально, потому что разрешено преобразование структуры* в char*. Но почему ((struct _type *)((char *)((_obj)->rest))) не работает? - person ProgDevel; 30.07.2014
comment
Псевдоним проверяется перед любым приведением (ну, зависит от уровня предупреждения). - person keltar; 30.07.2014