OO Polymorphismus in C, Aliasing-Probleme?

Me und ein Kollege versuchen, eine einfache polymorphe Klassenhierarchie zu erreichen. Wir arbeiten an einem eingebetteten System und dürfen nur einen C-Compiler verwenden. Wir haben eine grundlegende Designidee, die ohne Warnungen kompiliert wird (-Wall -Wextra -fstrict-aliasing -pedantic) und unter gcc 4.8.1 funktioniert.

Wir sind jedoch etwas besorgt über Aliasing-Probleme, da wir nicht vollständig verstehen, wann dies zu einem Problem wird.

Um zu demonstrieren, haben wir ein Spielzeugbeispiel mit einer 'Schnittstelle' IHello und zwei Klassen geschrieben, die diese Schnittstelle 'Katze' und 'Hund' implementieren.

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

Ausgabe

Schönen Tag! Ich bin eine Katze! Mein Name ist Mittens und ich bin 5 Jahre alt.

Hallo! Ich bin ein Hund! Ich kann diesen Sound machen: Woof! Ich bin 4 Jahre alt und wiege 10,3 kg.

Wir sind ziemlich sicher, dass der 'Upcast' von Cat and Dog zu IHello sicher ist, da IHello das erste Mitglied dieser beiden Strukturen ist.

Unser echtes Anliegen ist der "Downcast" von IHello zu Cat bzw. Dog in den entsprechenden Schnittstellenimplementierungen von SayHello. Verursacht dies strenge Aliasing-Probleme? Funktioniert unser Code garantiert nach dem C-Standard oder haben wir einfach Glück, dass dies mit gcc funktioniert?

Aktualisiere

Die Lösung, für die wir uns letztendlich entscheiden, muss Standard C sein und kann sich nicht auf z. gcc extensions. Der Code muss mit verschiedenen (proprietären) Compilern auf verschiedenen Prozessoren kompiliert und ausgeführt werden können.

Mit diesem 'Muster' wird beabsichtigt, dass der Client-Code Zeiger auf IHello erhält und daher nur Funktionen in der Schnittstelle aufrufen kann. Diese Aufrufe müssen sich jedoch unterschiedlich verhalten, je nachdem, welche Implementierung von IHello empfangen wurde. Kurz gesagt, wir möchten dasselbe Verhalten wie das OOP-Konzept von Schnittstellen und Klassen, die diese Schnittstelle implementieren.

Wir sind uns der Tatsache bewusst, dass der Code nur funktioniert, wenn die IHello-Schnittstellenstruktur als erstes Mitglied der Strukturen platziert wird, die die Schnittstelle implementieren. Dies ist eine Einschränkung, die wir gerne akzeptieren.

Gemäß: Verstößt der Zugriff auf das erste Feld einer Struktur über einen C-Cast gegen das strikte Aliasing?

§6.7.2.1 / 13:

In einem Strukturobjekt haben die Nicht-Bitfeldelemente und die Einheiten, in denen sich Bitfelder befinden, Adressen, die in der Reihenfolge, in der sie deklariert wurden, zunehmen. Ein Zeiger auf ein Strukturobjekt, der entsprechend konvertiert wurde, zeigt auf sein ursprüngliches Element (oder, wenn dieses Element ein Bitfeld ist, auf die Einheit, in der es sich befindet) und umgekehrt. Innerhalb eines Strukturobjekts befindet sich möglicherweise ein unbenannter Abstand, jedoch nicht am Anfang.

Die Aliasing-Regel lautet wie folgt (§6.5 / 7):

uf den gespeicherten Wert eines Objekts darf nur mit einem Wertausdruck eines der folgenden Typen zugegriffen werde

ein Typ, der mit dem effektiven Typ des Objekts kompatibel ist, eine qualifizierte Version eines Typs, der mit dem effektiven Typ des Objekts kompatibel ist, ein Typ, der der vorzeichenbehaftete oder vorzeichenlose Typ ist, der dem effektiven Typ des Objekts entspricht, ein Typ, der der vorzeichenbehaftete oder vorzeichenlose Typ ist, der einer qualifizierten Version des effektiven Typs des Objekts entspricht,ein Aggregat- oder Vereinigungstyp, der einen der oben genannten Typen unter seinen Mitgliedern enthält (einschließlich rekursiv eines Mitglieds eines Unteraggregats oder einer enthaltenen Vereinigung), oder ein Zeichentyp.

ntsprechend dem fünften Punkt oben und der Tatsache, dass Strukturen oben keine Auffüllung enthalten, sind wir ziemlich sicher, dass das 'Upcasting' einer abgeleiteten Struktur, die die Schnittstelle zu einem Zeiger auf die Schnittstelle implementiert, sicher ist, d.

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

Die große Frage ist, ob der bei der Implementierung der Schnittstellenfunktion (en) verwendete "Downcast" sicher ist. Wie oben zu sehen:

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

eachten Sie auch, dass die Signatur der Implementierungsfunktionen in @ geändert wir

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

und das Auskommentieren des 'Downcasts' wird ebenfalls kompiliert und funktioniert einwandfrei. Dadurch wird jedoch eine Compiler-Warnung für eine Nichtübereinstimmung der Funktionssignaturen generiert.

Antworten auf die Frage(4)

Ihre Antwort auf die Frage