Polimorfismo OO em C, aliasing issues?

Eu e um colega estamos tentando alcançar uma hierarquia de classes polimórfica simples. Estamos trabalhando em um sistema incorporado e estamos restritos a usar apenas um compilador C. Temos uma idéia básica de design que compila sem avisos (-Wall -Wextra -fstrict-aliasing -pedantic) e roda bem no gcc 4.8.1.

No entanto, estamos um pouco preocupados com problemas de aliasing, pois não entendemos completamente quando isso se torna um problema.

Para demonstrar, escrevemos um exemplo de brinquedo com um 'interface' IHello e duas classes implementando essa interface 'Cat' e '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;
}

Resultado:

Dia bom! Eu sou um gato! Meu nome é Mittens e tenho 5 anos.

Olá! Eu sou um cão! Eu posso fazer esse som: Woof! Tenho 4 anos e peso 10,3 kg.

Temos certeza de que o 'upcast' de gato e cachorro para o IHello é seguro, já que o IHello é o primeiro membro de ambas as estruturas.

Nossa preocupação real é o 'downcast' de IHello para Cat e Dog, respectivamente, nas implementações de interface correspondentes do SayHello. Isso causa algum problema estrito de alias? É garantido que nosso código funcione de acordo com o padrão C ou temos a sorte de trabalhar com o gcc?

Atualizar

A solução que decidimos usar deve ser o padrão C e não pode confiar, por exemplo extensões gcc. O código deve ser capaz de compilar e executar em diferentes processadores usando vários compiladores (proprietários).

A intenção com esse 'padrão' é que o código do cliente receba ponteiros para o IHello e, portanto, apenas possa chamar funções na interface. No entanto, essas chamadas devem se comportar de maneira diferente, dependendo da implementação do IHello que foi recebida. Em resumo, queremos um comportamento idêntico ao conceito OOP de interfaces e classes que implementam essa interface.

Estamos cientes do fato de que o código só funciona se a estrutura da interface IHello for colocada como o primeiro membro das estruturas que implementam a interface. Essa é uma limitação que estamos dispostos a aceitar.

De acordo com:O acesso ao primeiro campo de uma estrutura por meio de uma conversão C viola o aliasing estrito?

§6.7.2.1 / 13:

Dentro de um objeto de estrutura, os membros que não são de campo de bits e as unidades nas quais os campos de bits residem têm endereços que aumentam na ordem em que são declarados. Um ponteiro para um objeto de estrutura, convertido adequadamente, aponta para seu membro inicial (ou se esse membro é um campo de bits, depois para a unidade em que reside) e vice-versa. Pode haver preenchimento sem nome dentro de um objeto de estrutura, mas não no início.

A regra de aliasing tem a seguinte redação (§6.5 / 7):

Um objeto deve ter seu valor armazenado acessado apenas por uma expressão lvalue que possui um dos seguintes tipos:

um tipo compatível com o tipo efetivo do objeto,uma versão qualificada de um tipo compatível com o tipo efetivo do objeto,um tipo que é o tipo assinado ou não assinado correspondente ao tipo efetivo do objeto,um tipo que é o tipo assinado ou não assinado correspondente a uma versão qualificada do tipo efetivo do objeto,um tipo agregado ou de união que inclua um dos tipos mencionados acima entre seus membros (incluindo, recursivamente, um membro de uma união subagregada ou contida), ouum tipo de caractere.

De acordo com o quinto marcador acima e o fato de que as estruturas não contêm preenchimento no topo, temos certeza de que 'upcasting' uma estrutura derivada que implementa a interface para um ponteiro para a interface é segura, ou seja,

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

A grande questão é se o 'downcast' usado na implementação das funções da interface é seguro. Como visto acima:

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

Observe também que a alteração da assinatura das funções de implementação para

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

e comentar o 'downcast' também compila e roda bem. No entanto, isso gera um aviso do compilador para incompatibilidade de assinatura de função.

questionAnswers(2)

yourAnswerToTheQuestion