tutorials.de Buch-Aktion 05/2012
ERLEDIGT
NEIN
ANTWORTEN
12
ZUGRIFFE
1648
EMPFEHLEN
  • An Twitter übertragen
  • An Facebook übertragen
AUF DIESES THEMA
ANTWORTEN
  1. #1
    canfänger Tutorials.de Gastzugang
    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?
    Code C:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    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
     

  2. #2
    jsendrow Tutorials.de Gastzugang
    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)
     

  3. #3
    deepthroat deepthroat ist offline Mitglied Diamant
    tutorials.de Premium-User
    Registriert seit
    Jun 2005
    Beiträge
    8.168
    Zitat Zitat von canfänger Beitrag anzeigen
    Hi!

    Ich würde gern wissen ob man C-strukturen verschachteln kann um eine Art Vererbung wie in C++ etc. zu simulieren
    Man kann. Du könntest dir z.B. mal die GLib und insbesondere das Typsystem und GObject (http://library.gnome.org/devel/gobje...r-gobject.html) anschauen.

    Gruß
     
    If at first you don't succeed, try again. Then quit. No use being a damn fool about it.

  4. #4
    deepthroat deepthroat ist offline Mitglied Diamant
    tutorials.de Premium-User
    Registriert seit
    Jun 2005
    Beiträge
    8.168
    Zitat Zitat von jsendrow Beitrag anzeigen
    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ß
    Geändert von deepthroat (29.01.10 um 15:20 Uhr)
     
    If at first you don't succeed, try again. Then quit. No use being a damn fool about it.

  5. #5
    Registriert seit
    Dec 2001
    Ort
    Bayern
    Beiträge
    5.802
    Blog-Einträge
    5
    Zitat Zitat von jsendrow Beitrag anzeigen
    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 )
    C kennt per se keine Vererbung im objektorientierten Sinne. Man wird also immer auf andere Methoden ausweichen müssen, wenn man eine Art Vererbung in C implementieren will.

    Grüße,
    Matthias
     
    „Gib einem Menschen einen Fisch, und er wird für einen Tag satt. Lehre ihn Fischen, und er wird ein Leben lang satt.“
    “For every complex problem, there is an answer that is short, simple and wrong.”
    “Pessimism is safe, but optimism is a lot faster!”


    Aktuelles Coding Quiz: #17 - Wörter kreuz und quer

  6. #6
    canfänger Tutorials.de Gastzugang
    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
     

  7. #7
    deepthroat deepthroat ist offline Mitglied Diamant
    tutorials.de Premium-User
    Registriert seit
    Jun 2005
    Beiträge
    8.168
    Zitat Zitat von canfänger Beitrag anzeigen
    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 :
    1
    
    &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ß
     
    If at first you don't succeed, try again. Then quit. No use being a damn fool about it.

  8. #8
    Avatar von Vereth
    Vereth Vereth ist offline Mitglied Brokat
    Registriert seit
    Nov 2009
    Ort
    Dortmund
    Beiträge
    372
    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.
    Geändert von Vereth (30.01.10 um 08:09 Uhr)
     
    Vielen Dank für die Nutzung des Bewerten- und Danke-Buttons

    Wenn man sieht, dass man einen anderen glücklich gemacht hat, ist die Welt um zwei glückliche Menschen reicher.

  9. #9
    canfänger Tutorials.de Gastzugang
    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
     

  10. #10
    Avatar von Vereth
    Vereth Vereth ist offline Mitglied Brokat
    Registriert seit
    Nov 2009
    Ort
    Dortmund
    Beiträge
    372
    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/.
     
    Vielen Dank für die Nutzung des Bewerten- und Danke-Buttons

    Wenn man sieht, dass man einen anderen glücklich gemacht hat, ist die Welt um zwei glückliche Menschen reicher.

  11. #11
    Registriert seit
    Dec 2001
    Ort
    Bayern
    Beiträge
    5.802
    Blog-Einträge
    5
    Zitat Zitat von Vereth Beitrag anzeigen
    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.
    Das stimmt nicht uneingeschränkt. Es gibt eine Vielzahl von Aufrufkonventionen. Bei einigen muss der aufrufende Programmteil den Stack aufräumen, bei anderen erledigt das das aufgerufene Unterprogramm. Auch müssen Parameter nicht immer über den Stack übergeben werden, man kann dazu z.B. auch die CPU-Register verwenden. Die meisten Compiler für die x86-Architektur übergeben den Rückgabewert einer Funktion beispielsweise über das EAX-Register.

    Zitat Zitat von Vereth Beitrag anzeigen
    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.
    Nein. Wie die Funktion aufzurufen ist, bestimmt schon der Zeigertyp. Der Zeiger verweist lediglich auf den Einsprungspunkt der Funktion.

    Zitat Zitat von Vereth Beitrag anzeigen
    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.
    Doch, bei Funktionszeigern findet im Allgemeinen eine zusätzliche Dereferenzierung statt. Eine normaler Funktionsaufruf (ohne Funktionszeiger) entspricht einem Sprung an eine feste Adresse im Speicherbereich des Programms. Diese ermittelt der Linker zur Kompilierzeit und trägt sie an den entsprechenden Stellen ein. Dagegen steht die Adresse, auf die ein Funktionszeiger verweist, erst zur Laufzeit fest. Das Programm muss also zuerst diese Adresse auslesen und kann erst dann den Sprung ausführen.

    Grüße,
    Matthias
     
    „Gib einem Menschen einen Fisch, und er wird für einen Tag satt. Lehre ihn Fischen, und er wird ein Leben lang satt.“
    “For every complex problem, there is an answer that is short, simple and wrong.”
    “Pessimism is safe, but optimism is a lot faster!”


    Aktuelles Coding Quiz: #17 - Wörter kreuz und quer

  12. #12
    Avatar von Vereth
    Vereth Vereth ist offline Mitglied Brokat
    Registriert seit
    Nov 2009
    Ort
    Dortmund
    Beiträge
    372
    Wer den Stack aufräumt, ist letzlich egal; es ist eine Arbeit, die in jedem Fall gemacht werden muss. Allerdings denke ich, dass die aufgerufene Funktion immer einen gewissen Teil selber machen muss, da nur sie weiß, wie viele auto-Variablen sie angelegt hat.
    Der Zeiger verweist lediglich auf den Einsprungspunkt der Funktion, das ist richtig. Aber bei Funktionen, die nicht statisch gebunden wurden, weil sie z.B. in einer DLL sind, verweist der Funktionszeiger auf einen Stub, der dafür sorgt, dass diese DLL auch geladen und ausgeführt wird. Aber das ist für die eigentliche Thematik irrelevant.
    Ich sehe ein, dass eine zusätzliche Dereferenzierung notwendig ist. Aber darüber hinaus entsteht wohl kein zusätzlicher Aufwand. Zudem ist die Dereferenzierung eine schnelle Operation, die auf die Laufzeit eines Programms kaum Auswirkung hat.
     
    Vielen Dank für die Nutzung des Bewerten- und Danke-Buttons

    Wenn man sieht, dass man einen anderen glücklich gemacht hat, ist die Welt um zwei glückliche Menschen reicher.

  13. #13
    Registriert seit
    Dec 2001
    Ort
    Bayern
    Beiträge
    5.802
    Blog-Einträge
    5
    Zitat Zitat von Vereth Beitrag anzeigen
    Wer den Stack aufräumt, ist letzlich egal; es ist eine Arbeit, die in jedem Fall gemacht werden muss. Allerdings denke ich, dass die aufgerufene Funktion immer einen gewissen Teil selber machen muss, da nur sie weiß, wie viele auto-Variablen sie angelegt hat.
    Klar. Ich wollte nur aufzeigen, dass es unterschiedliche Aufrufkonventionen gibt.

    Zitat Zitat von Vereth Beitrag anzeigen
    Der Zeiger verweist lediglich auf den Einsprungspunkt der Funktion, das ist richtig. Aber bei Funktionen, die nicht statisch gebunden wurden, weil sie z.B. in einer DLL sind, verweist der Funktionszeiger auf einen Stub, der dafür sorgt, dass diese DLL auch geladen und ausgeführt wird.
    Auch das ist wieder nicht die ganze Wahrheit. Du beschreibst hier das verzögerte Laden von DLLs. Das ist aber nicht die einzige Möglichkeit, DLLs zu verwenden. Im Regelfall werden DLLs schon beim Programmstart geladen und die Adressen der Einstiegspunkte der DLL-Funktionen in die Import-Adresstabelle (IAT) eingetragen. Beim Aufruf einer DLL-Funktion wird dann an die entsprechende Stelle in der IAT gesprungen. Dort ist ein Sprungbefehl an die vom Lader eingetragene Adresse enthalten, welcher als nächstes ausgeführt wird. Man kann die DLL außerdem noch „per Hand“ laden (LoadLibrary) und sich die Einstiegspunkte holen (GetProcAddress) – beim „automatischen“ verzögerten Laden geschieht das genauso.

    Aber wie du schon sagtest:
    Zitat Zitat von Vereth Beitrag anzeigen
    Aber das ist für die eigentliche Thematik irrelevant.


    Grüße,
    Matthias
     
    „Gib einem Menschen einen Fisch, und er wird für einen Tag satt. Lehre ihn Fischen, und er wird ein Leben lang satt.“
    “For every complex problem, there is an answer that is short, simple and wrong.”
    “Pessimism is safe, but optimism is a lot faster!”


    Aktuelles Coding Quiz: #17 - Wörter kreuz und quer

Ähnliche Themen

  1. Liste für 2 Structs
    Von Need_Name im Forum C/C++
    Antworten: 8
    Letzter Beitrag: 04.01.10, 14:49
  2. C++ structs lesen
    Von Orthak im Forum C/C++
    Antworten: 7
    Letzter Beitrag: 13.01.09, 21:21
  3. sort vector von structs
    Von Thomasio im Forum C/C++
    Antworten: 9
    Letzter Beitrag: 17.03.08, 15:38
  4. Protokollsachen: Structs aus C++ übernehmen?
    Von BeaTBoxX im Forum .NET Application und Service Design
    Antworten: 3
    Letzter Beitrag: 15.12.06, 13:39
  5. [C] Warnung bei structs
    Von rookie im Forum C/C++
    Antworten: 3
    Letzter Beitrag: 10.02.03, 12:55