OO Polimorfismo en C, ¿problemas de alias?

Un colega y yo estamos tratando de lograr una jerarquía de clases polimórfica simple. Estamos trabajando en un sistema embebido y estamos restringidos a usar solo un compilador de C. Tenemos una idea de diseño básica que se compila sin advertencias (-Wall -Wextra -fstrict-aliasing -pedantic) y funciona bien bajo gcc 4.8.1.

Sin embargo, estamos un poco preocupados por los problemas de alias ya que no entendemos completamente cuándo esto se convierte en un problema.

Para demostrarlo, hemos escrito un ejemplo de juguete con una 'interfaz' IHello y dos clases que implementan esta interfaz 'Cat' y '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;
}

Salida:

¡Buen día! ¡Soy un gato! Mi nombre es Mittens y tengo 5 años.

¡Hola! ¡Soy un perro! Puedo hacer que suene: ¡Guau! Tengo 4 años y peso 10.3 kg.

Estamos bastante seguros de que el 'upcast' de Cat and Dog a IHello es seguro ya que IHello es el primer miembro de estas dos estructuras.

Nuestra verdadera preocupación es el 'downcast' de IHello a Cat and Dog respectivamente en las implementaciones de interfaz correspondientes de SayHello. ¿Esto causa problemas de alias estrictos? ¿Nuestro código está garantizado para funcionar con el estándar C o simplemente tenemos suerte de que esto funcione con gcc?

Actualizar

La solución que finalmente decidamos usar debe ser el estándar C y no se puede confiar, p. Extensiones de gcc. El código debe poder compilarse y ejecutarse en diferentes procesadores utilizando varios compiladores (propietarios).

La intención con este 'patrón' es que el código del cliente reciba punteros a IHello y, por lo tanto, solo pueda llamar a funciones en la interfaz. Sin embargo, estas llamadas deben comportarse de manera diferente según la implementación de IHello que se recibió. En resumen, queremos un comportamiento idéntico al concepto OOP de interfaces y clases que implementan esta interfaz.

Somos conscientes del hecho de que el código solo funciona si la estructura de la interfaz IHello se coloca como el primer miembro de las estructuras que implementan la interfaz. Esta es una limitación que estamos dispuestos a aceptar.

De acuerdo a:¿El acceso al primer campo de una estructura a través de un elenco C viola el alias estricto?

§6.7.2.1 / 13:

Dentro de un objeto de estructura, los miembros que no son campos de bits y las unidades en las que residen los campos de bits tienen direcciones que aumentan en el orden en que se declaran. Un puntero a un objeto de estructura, convertido adecuadamente, apunta a su miembro inicial (o si ese miembro es un campo de bits, luego a la unidad en la que reside), y viceversa. Puede haber relleno sin nombre dentro de un objeto de estructura, pero no al principio.

La regla de alias dice lo siguiente (§6.5 / 7):

Un objeto tendrá acceso a su valor almacenado solo mediante una expresión lvalue que tenga uno de los siguientes tipos:

un tipo compatible con el tipo efectivo del objeto,una versión calificada de un tipo compatible con el tipo efectivo del objeto,un tipo que es el tipo con signo o sin signo correspondiente al tipo efectivo del objeto,un tipo que es el tipo con signo o sin signo correspondiente a una versión calificada del tipo efectivo del objeto,un tipo agregado o de unión que incluye uno de los tipos antes mencionados entre sus miembros (incluido, recursivamente, un miembro de una unión agregada o contenida), oun tipo de personaje

De acuerdo con la quinta viñeta anterior y el hecho de que las estructuras no contienen relleno en la parte superior, estamos bastante seguros de que la 'transmisión' de una estructura derivada que implementa la interfaz a un puntero a la interfaz es segura, es decir.

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 */
}

La gran pregunta es si el 'downcast' usado en la implementación de las funciones de la interfaz es seguro. Como se ve arriba:

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 */
}

También tenga en cuenta que cambiar la firma de las funciones de implementación a

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

y comentar el 'downcast' también se compila y funciona bien. Sin embargo, esto genera una advertencia del compilador para la falta de coincidencia de firma de función.

Respuestas a la pregunta(2)

Su respuesta a la pregunta