C-structs und Vererbung?!

  • Themenstarter Themenstarter canfänger
  • Beginndatum Beginndatum
C

canfänger

Hi!

Ich würde gern wissen ob man C-strukturen verschachteln kann um eine Art Vererbung wie in C++ etc. zu simulieren,. Der Code unten produziert bei mir keine Fehler, doch soweit mir das bekannt ist optimieren die Compiler die interna von Structs (Alignment etc.) - kann ich dennoch guten Gewissens so arbeiten?
C:
typedef struct {
    unsigned length;
    unsigned width;
} quad_t;

typedef struct {
    quad_t base;
    unsigned height;
} cube_t;

static int
print_quad_values(FILE *stream, quad_t *quad) {
    return fprintf( stream, "Length: %u\nWidth: %u\n",
        quad->length, quad->width );
}

/* .. */

int
main(void) {
/* .. */
    cube_t *cube = (cube_t*) malloc(sizeof(cube_t));
/* .. */
    print_quad_values(stderr, (quad_t*) cube);
/* .. */
    free(cube);
/* .. */
    return 0;
}

Danke im Vorraus
Peter
 
Das hat mit Vererbung nix zu tuen. Der Fachbegriff für das, was Du da machst ist Composition ( http://en.wikipedia.org/wiki/Object_composition )

Vererbung basiert auf ein IS-Beziehung (a car IS a moving object), Komposition auf einer HAS-Beziehung (a car HAS a engine)
Die OOP Geschichte von der du sprichst, ist doch nichts weiter als ein philosophisches Konzept. Die Frage ist letztendlich wie man das im Rechner umsetzen kann.

Natürlich hat canfänger hier eine Art Vererbung erreicht denn (abstrakt gesehen) ist eine cube_t eben immer auch ein quad_t.

Auf der Objektcode-Ebene wurde hier lediglich ein Typ erstellt der mit einen anderen benutzerdefinierten Typ enthält. Etwas anderes passiert bei C++ auch nicht.

Gruß
 
Zuletzt bearbeitet:
Danke für die Antworten!

Wie deepthroat schon vermutet hat, es ist mir ziemlich egal ob mein Beispiel oben nun wirklich Vererbung oder wasweisich ist. Mich interessierte nur, ob es eine sichere Operation ist, einen Pointer auf ein cube_t in einen Pointer auf ein quad_t zu casten (Zeile 24).
Ich habe schon gesehen dass man einfach die Felder im basis-Typ zu beginn des erweiterten Types wiederholt, will aber das Tippen und eventuelle Aktualisieren an mehreren Stellen umgehen.

Vielen Dank nochmal!
Peter
 
Mich interessierte nur, ob es eine sichere Operation ist, einen Pointer auf ein cube_t in einen Pointer auf ein quad_t zu casten (Zeile 24).
Beantwortet ist die Frage ja eigentlich schon, aber es ist sogar logisch ;) Für ein struct S gilt immer:
Code:
&S == &S.firstMember
D.h. die Adresse wo die Struktur im Speicher liegt ist immer gleich der Adresse des ersten Members. Also es gibt höchstens Padding aufgrund von Alignment zwischen den Komponenten einer Struktur, aber nie am Anfang.

Gruß
 
Du kannst in einem struct sogar Zeiger auf Funktionen speichern. Wenn du die richtig setzt, kannst du sie aufrufen, als wären es (Klassen-) Methoden. ;-)
 
Zuletzt bearbeitet:
Ja, aber...

ist das dann nicht ähnlich den virtuellen Funktionen in C++? Ok, hier habe ich keine vptr, bzw. keine komplette Tabelle, aber ein Funktionsaufruf über Zeiger ist doch resourcenlastiger als ein handgeschriebener aufruf einer Funktion - vorrausgesetzt der Compiler "inlined" die Anweisungen nicht..?

Gruß
Peter
 
Mein Post war eher scherzhaft gemeint, aber das Thema scheint dich derartig zu interessieren, dass ich dir eine ausführlichere Darstellung gönne.
Der Bezeichner einer Funktion ist immer ein Zeiger auf diese Funktion. Der Zeiger verweist dabei allerdings nicht auf Daten, sondern auf ausführbaren Programmcode. Deswegen kann man auch Variablen deklarieren, denen eine Funktion (genauer den Zeiger darauf) zugewiesen werden kann. Bei der Deklaration muss diese Funktionszeiger-Variable dieselbe Signatur haben, wie die Funktion, die dieser zugewiesen werden soll, d.h. der Rückgabewert und die Parameter müssen dieselben Datentypen haben. Wenn dieser Variablen eine gültige Funktion zugewiesen wurde, dann kann diese Funktion über die Variable genauso aufgerufen werden, als wenn diese die Funktion selber wäre.
Beim Aufrufen einer Funktion werden immer als erstes die Parameter auf den Stack gepusht; diese Arbeit erledigt stets das aufrufende Programm. Das gilt auch für das Zurückliefern des Funktionsaufrufes, der durch das aufrufende Programm vom Stack gepopt wird. Der Zeiger verseist im wesentlichen auf die Informationen, wie diese Funktion aufzurufen ist, ob über ein zu ladendes Modul oder, bei selbst geschriebenen Funktionen, direkt in den Code des laufenden Programms. Deswegen ist der Ablauf beim Aufruf einer Funktion immer der gleiche, egal, ob du die Funktion über einen Funktionszeiger oder über den 'Original'-Namen aufrufst; eine zusätzliche Dereferenzierung findet nicht statt. Es existiert also weder über zusätzlichen Speicherplatz noch über eine zusätzliche Dereferenzierung eine Mehrbelastung der Systemressourcen. Die Deklaration eines Funktionszeigers und die Zuweisung einer gültigen Funktion an diese Zeigervariable ist der einzige Mehrbedarf, der dabei anfällt.
Meistens wird dieses Feature nicht benötigt. Allerdings gibt es manchmal Problemstellungen, in denen die Verwendung von Funktionszeigern sinnvoll ist. Ein klassisches Beispiel ist die Übergabe einer Vergleichs-Funktion an eine Sortierroutine, z.B. qsort. Eine gute, allerdings englische, Beschreibung dieses Themas findest du bei http://www.newty.de/.
 
Zurück