О.О. Полиморфизм в Си, проблемы алиасинга?

Я и коллега пытаемся достичь простой полиморфной иерархии классов. Мы работаем над встроенной системой и ограничены только использованием компилятора Си. У нас есть базовая идея проекта, которая компилируется без предупреждений (-Wall -Wextra -fstrict-aliasing -pedantic) и отлично работает в gcc 4.8.1.

Тем не менее, мы немного беспокоимся о проблемах совмещения имен, так как не до конца понимаем, когда это становится проблемой.

Для демонстрации мы написали игрушечный пример с «интерфейсом» IHello и двумя классами, реализующими этот интерфейс «Cat» и «Dog».

#include <stdio.h>

/* -------- IHello -------- */
struct IHello_;
typedef struct IHello_
{
    void (*SayHello)(const struct IHello_* self, const char* greeting);
} IHello;

/* Helper function */
void SayHello(const IHello* self, const char* greeting)
{
    self->SayHello(self, greeting);
}

/* -------- Cat -------- */
typedef struct Cat_
{
    IHello hello;
    const char* name;
    int age;
} Cat;

void Cat_SayHello(const IHello* self, const char* greeting)
{
    const Cat* cat = (const Cat*) self;
    printf("%s I am a cat! My name is %s and I am %d years old.\n",
           greeting,
           cat->name,
           cat->age);
}

Cat Cat_Create(const char* name, const int age)
{
    static const IHello catHello = { Cat_SayHello };
    Cat cat;

    cat.hello = catHello;
    cat.name = name;
    cat.age = age;

    return cat;
}

/* -------- Dog -------- */
typedef struct Dog_
{
    IHello hello;
    double weight;
    int age;
    const char* sound;
} Dog;

void Dog_SayHello(const IHello* self, const char* greeting)
{
    const Dog* dog = (const Dog*) self;
    printf("%s I am a dog! I can make this sound: %s I am %d years old and weigh %.1f kg.\n",
           greeting,
           dog->sound,
           dog->age,
           dog->weight);
}

Dog Dog_Create(const char* sound, const int age, const double weight)
{
    static const IHello dogHello = { Dog_SayHello };
    Dog dog;

    dog.hello = dogHello;
    dog.sound = sound;
    dog.age = age;
    dog.weight = weight;

    return dog;
}

/* Client code */
int main(void)
{
    const Cat cat = Cat_Create("Mittens", 5);
    const Dog dog = Dog_Create("Woof!", 4, 10.3);

    SayHello((IHello*) &cat, "Good day!");
    SayHello((IHello*) &dog, "Hi there!");

    return 0;
}

Выход:

Добрый день! Я кот! Меня зовут варежки и мне 5 лет.

Всем привет! Я собака! Я могу издать этот звук: Гав! Мне 4 года и я вешу 10,3 кг.

Мы почти уверены, что «upcast» от Cat и Dog до IHello безопасен, поскольку IHello является первым участником обеих этих структур.

Наше настоящее беспокойство вызывает «снижение» от IHello до Cat и Dog соответственно в соответствующих реализациях интерфейса SayHello. Это вызывает какие-либо строгие проблемы с наложением? Наш код гарантированно работает по стандарту C или нам просто повезло, что это работает с gcc?

Обновить

Решение, которое мы в конечном итоге решим использовать, должно быть стандартным C и не может полагаться, например, на Расширения GCC. Код должен быть способен компилироваться и запускаться на разных процессорах с использованием различных (проприетарных) компиляторов.

Цель этого «шаблона» состоит в том, чтобы клиентский код получал указатели на IHello и, следовательно, мог вызывать только функции в интерфейсе. Однако эти вызовы должны вести себя по-разному в зависимости от того, какая реализация IHello была получена. Короче говоря, мы хотим, чтобы поведение идентично концепции ООП интерфейсов и классов, реализующих этот интерфейс.

Нам известно о том, что код работает только в том случае, если структура интерфейса IHello помещена в качестве первого члена структур, реализующих интерфейс. Это ограничение, которое мы готовы принять.

В соответствии с:Доступ к первому полю структуры через приведение C нарушает строгий псевдоним?

§6.7.2.1 / 13:

Внутри объекта структуры члены без битовых полей и блоки, в которых находятся битовые поля, имеют адреса, которые увеличиваются в порядке их объявления. Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный элемент (или, если этот элемент является битовым полем, затем на модуль, в котором он находится), и наоборот. Внутри объекта структуры может быть безымянный отступ, но не в его начале.

Правило наложения имен гласит следующее (§6.5 / 7):

Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов:

тип, совместимый с эффективным типом объекта,квалифицированная версия типа, совместимого с эффективным типом объекта,тип, который является типом со знаком или без знака, соответствующим действующему типу объекта,тип, который является типом со знаком или без знака, соответствующим квалифицированной версии действующего типа объекта,агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегированного или автономного объединения), илитип персонажа.

В соответствии с пятым пунктом выше и тем фактом, что структуры не содержат отступов в верхней части, мы достаточно уверены, что «выгрузка» производной структуры, которая реализует интерфейс для указателя на интерфейс, безопасна, т.е.

Cat cat;
const IHello* catPtr = (const IHello*) &cat; /* Upcast */

/* Inside client code */
void Greet(const IHello* interface, const char* greeting)
{
    /* Users do not need to know whether interface points to a Cat or Dog. */
    interface->SayHello(interface, greeting); /* Dereferencing should be safe */
}

Большой вопрос в том, безопасен ли «downcast», используемый при реализации функции интерфейса. Как видно выше:

void Cat_SayHello(const IHello* hello, const char* greeting)
{
    /* Is the following statement safe if we know for
     * a fact that hello points to a Cat?
     * Does it violate strict aliasing rules? */
    const Cat* cat = (const Cat*) hello;
    /* Access internal state in Cat */
}

Также обратите внимание, что изменение подписи функций реализации на

Cat_SayHello(const Cat* cat, const char* greeting);
Dog_SayHello(const Dog* dog, const char* greeting);

и комментирование 'downcast' также компилируется и работает нормально. Однако это генерирует предупреждение компилятора о несоответствии сигнатуры функции.

Ответы на вопрос(2)

Ваш ответ на вопрос