Einen struct von einem anderen ableiten?!?



  • Hi,
    ich schreibe gerade eine Bibliothek, die unteranderem 2 Funktionen haben soll, wie in etwa so:
    struct foo* Create(); und
    void Destroy(struct foo*);

    Das zurückgegebene "foo" wird noch für diverse andere Funktionen der Bibliothek als Parameter (instanz) übergeben und dort verwendet, allerdings wird das Hauptprogramm auch einige Elemente von foo verwenden. Darum habe ich auch in der veröffentlichten Header-Datei nur diese vom Hauptprogramm verwendeten Elemente aufgelistet, da man an die anderen auch nicht ohne besonderes Wissen herankommen soll.

    Nun suche ich aber eine elegante Methode, um innerhalb der Bibliothek an die zusätzlichen Elemente heranzukommen. In Create() habe ich natürlich ausreichend Speicher reserviert, also malloc(sizeof(struct foo)+XYZ). Innerhalb der Lib greife ich nun mittels

    *(<type*>)&(((char*)foo_ptr)[sizeof(struct foo)+<offset>])
    

    auf diese zusätzlichen Elemte zu, aber das ist doch mist! 😉

    Wie macht man soetwas richtig? Kann ich z.B. einfach "ableiten" ? Hat bei mir aber nicht geklappt...

    Ich hatte noch eine andere Idee, und zwar könnte man einfach ein struct foo_extended schreiben, welches wie foo ist, allerdings um die entsprechenden Elemente erweitert. Allerdings bin ich mir dann nicht sicher, ob der Compiler die Reihenfolge der Elemente ändert bzw. ändern darf, weil ich nicht weiss, was der C-Standard dem Compiler vorschreibt.

    Kennt sich damit jemand aus?
    Danke!



  • Das was du willst gibts meiner Meinung nach erst in C++
    3 Lösungsansätze

    1 Du gibst alle Elemente in der Struktur an, machst den Privaten Teil durch einen Kommentar kenntlich. (willst du wahrscheinlich nicht)

    2 Du arbeitest mit 2 unabhängigen Strukturen, die eine mit den öffentlichen die andere mit den privaten Elementen. Die eine ist in dem öffentlichen Header definiert, die andere in einem Header der nur bei der Bibliothekskompiulation benutzt wird.

    3 Du arbeitest mit 2 Strukturen und definierst die öffentlich Struktur mit einem Pointer auf die "private" Struktur somit ist die Verbindung/Abhängigkeit besser sichtbar.

    🙂



  • Die 2. Variante scheint mir die schönste zu sein. Wenn ich es richtig verstanden habe, dann beschreibst Du damit das, was ich mir auch überlegt habe (siehe letzter Abschnitt). Allerdings möchte ich sichergehen, dass der Compiler die Reihenfolge der Elementer auch in der Reihenfolge belässt, wie sie im strukt vorkommen (damit ein cast vom einen auf den anderen Type bei den gemeinsamen Elementen kompatiblität beibehält). Ich gehe mal davon aus, dass der Compiler die Reihenfolge auch so belässt, aber was der C-Standard über die Rheinenfolge der Elemente sagt, würde mich dafür sehr interessieren.



  • Wofür cast??

    //Private Struktur diese findet sich nur in einer Header Datei die bei der Erstellung der Bibliothek benutzt wird
    typedef struct
    {
    float wertA;
    }  MyPrivateData;
    //öffentliche Struktur diese findet sich in der allgemeinen Header Datei
    typedef struct
    {
    int wert1;
    unsigned int wert2;
    double wert3;
    }  MyPublicData;
    

    Die beiden Structs werden nicht aufeinander gecastet.
    Die Biblothek nutzt sowohl MyPublicData als auch MyPrivateData
    Die Benutzer der Bibliothek können nur MyPublicData nutzen.

    Deswegen würde ich auch die Methode 3 vorziehen, dann kann ich die Bibliothek mehrfach in einem Programm nutzen.
    Dies sähe dann so aus:

    typedef struct
    {
    int wert1;
    unsigned int wert2;
    double wert3;
    void *Data;
    }  MyPublicData;
    

    Das heißt jedesmal wenn ich eine Variable vom Typ MyPublicData anlege ist schon eine Pointer für die zugehörigen privateData angelegt.

    D.h nach dem Anlegen muss eine Funktion aus der Bibliothek aufgerufen werden (deine Create funktion) die
    1. das Memory für die Private Data allokiert und
    2. diese initialisiert.

    und es muss eine Funktion existiern die diese Allokierungen wieder aufhebt (deine Destroy)
    Den Pointer auf void habe ich deswegen gewählt, da dir scheinbar das Information hiding sehr wichtig ist,
    falls nicht kannst anstelle von void * auch eine Pointer aud MyPrivatData benutzen.
    😃

    Das casten von Strukturen auf Strukturen in der Art wie du es anwendest halte ich für schlechten Stil (habe so eigentlich auch noch nicht gesehen, außerdem könnte es technisch alignment Probleme geben, deswegen sollte man es nicht tun. 😞



  • @wischmop2

    Ich hatte noch eine andere Idee, und zwar könnte man einfach ein struct foo_extended schreiben, welches wie foo ist, allerdings um die entsprechenden Elemente erweitert.

    im sourcecode vom spiel homeworld findet man etwas in dieser richtung, hier ein kleiner auszug aus dem src:

    typedef struct
    {
        Node objlink;
        ObjType objtype;        // object type (ship or bullet, etc)
        udword flags;           // flags whether the object is rotatable, etc.
        StaticInfo *staticinfo; // pointer to static info
        Node renderlink;        // link for rendering list
        ubyte currentLOD;       // current level of detail
        ubyte renderedLODs;     //what LODs have been rendered already
        uword attributes;               // settable attributes from mission editor
        sword attributesParam;           // parameter for attributes, set from mission editor
        ubyte attributesPad[2];          // leftover, free space
        real32 cameraDistanceSquared;    // distance to camera (for sound, level of detail)
        vector cameraDistanceVector;     // vector to camera (camera eyeposition - obj->posinfo.position)
        real32 collOptimizeDist;         // distance used for optimizing collision checking
        struct blob *collMyBlob;         // collision blob I belong to
        PosInfo posinfo;
    } SpaceObj;
    

    ...

    typedef struct SpaceObjRotImpTarg
    {
        Node objlink;
        ObjType objtype;        // object type (ship or bullet, etc)
        udword flags;            // flags whether the object is rotatable, etc.
        StaticInfo *staticinfo; // pointer to static info
        Node renderlink;        // link for rendering list
        ubyte currentLOD;       // current level of detail
        ubyte renderedLODs;     //what LODs have been rendered already
        uword attributes;               // settable attributes from mission editor
        sword attributesParam;          // parameter for attributes, set from mission editor
        ubyte attributesPad[2];          // leftover, free space
        real32 cameraDistanceSquared;    // distance to camera (for sound, level of detail)
        vector cameraDistanceVector;     // vector to camera (camera eyeposition - obj->posinfo.position)
        real32 collOptimizeDist;         // distance used for optimizing collision checking
        struct blob *collMyBlob;         // collision blob I belong to
        PosInfo posinfo;
        RotInfo rotinfo;
        CollInfo collInfo;
        Node impactablelink;
        real32 health;                  // hitpoints
        real32 flashtimer;              // determines if object flashes on screen
        real32 lasttimecollided;
    
    } SpaceObjRotImpTarg;
    

    ...

    typedef struct DustCloud
    {
        Node objlink;
        ObjType objtype;        // object type (ship or bullet, etc)
        udword flags;           // flags whether the object is rotatable, etc.
        ResourceStaticInfo *staticinfo; // pointer to static info
        Node renderlink;        // link for rendering list
        ubyte currentLOD;       // current level of detail
        ubyte renderedLODs;     //what LODs have been rendered already
        uword attributes;               // settable attributes from mission editor
        sword attributesParam;          // parameter for attributes, set from mission editor
        ubyte attributesPad[2];          // leftover, free space
        real32 cameraDistanceSquared;    // distance to camera (for sound, level of detail)
        vector cameraDistanceVector;     // vector to camera (camera eyeposition - obj->posinfo.position)
        real32 collOptimizeDist;         // distance used for optimizing collision checking
        struct blob *collMyBlob;         // collision blob I belong to
        PosInfo posinfo;
        RotInfo rotinfo;
        CollInfo collInfo;
        Node impactablelink;
        real32 health;                  // hitpoints
        real32 flashtimer;              // determines if object flashes on screen
        real32 lasttimecollided;
    
        Node resourcelink;
        ResourceID resourceID;
        uword resourceNotAccessible;
        sdword resourcevalue;
        struct ResourceVolume *resourceVolume;      // resourceVolume I belong to
    
        // Dust Cloud specific stuff
        DustCloudType dustcloudtype;
        real32 scaling;
        cloudSystem* stub;
    } DustCloud;
    

    benutzungsbeispiele:

    void ApplyDamageToTarget(SpaceObjRotImpTarg *target, ...)
    {
        ...
        switch (target->objtype)
        {
            ...
            case OBJ_AsteroidType:
            ...
            case OBJ_DustType:
                ...
                DustCloudChargesUp((DustCloud*)target, ...
            ...
        }
        ...
    }
    
    DustCloud *univAddDustCloud(DustCloudType dustcloudtype,vector *cloudpos)
    {
        DustCloud* newCloud;
        ...
        newCloud = memAlloc(sizeof(DustCloud),"DustCloud",NonVolatile);
        ...
        newCloud->objtype = OBJ_DustType;
        ...
        return newCloud;
    }
    
    DustCloud *dustCloud = univAddDustCloud(DustCloud0,&ship->posinfo.position);
    HandleDustCloudScaling(dustCloud);
    univAddObjToRenderListIf((SpaceObj *)dustCloud,(SpaceObj *)ship);
    


  • @bozo: Jaaa, ganz genau das meine ich. Das hat nämlich gegenüber den andere Methoden den Vorteil, das wirklich nichts zusätzliches sichtbar ist (auch kein void *data) und imho noch wichtiger, es reicht 1x Speicher zu reservieren (und ein free).
    Die einzige Frage, die mich immer noch quält ist die, ob dieses Vorgehen nur mit (fast) jedem Compiler glücklicherweise Funktioniert, oder ob der C-Standard dies auch explizit zulässt. Oder anders formuliert: Wenn ich zwei structs definiere, sagen wir mal

    struct foo1 {
      <type_1> var_1;
      ...
      <type_n> var_n;
      <type_j> var_n+1;
      ...
    };
    

    und

    struct foo2 {
      <type_1> var_1;
      ...
      <type_n> var_n;
      <type_k> var_n+1;
      ...
    };
    

    gilt dann nach C-Standard grundsätzlich

    &(((struct foo1*)a_ptr)->var_i)==&(((struct foo2*)a_ptr)->var_i)
    

    für 1<=i<=n ? Es könnte ja sein, dass der Compiler die Variablen von foo1 in einer ganz anderen Reihenfolge anordnet als die Variablen von foo2. Auch wenn es dafür keinen besonderen Grund gibt, warum er es machen sollte, will ich nur sichergehen, ob der C-Standard dazu etwas sagt. ("Glauben ist gut, wissen ist besser" o) )



  • Wenn man das auf diese eigenartige Weise manchen möchte dann doch am bestn mit einer union.

    typedef struct
    {
    int wert1;
    unsigned int wert2;
    double wert3;
    }  MyPublicData;
    
    typedef struct
    {
    int wert1;
    unsigned int wert2;
    double wert3;
    // ab hier  zusätzliche Elemente
    int wert4;
    }  MyPrivateData;
    
    union 
    {
    MyPrivateData A;
    MyPublicData  B;
    } test;
    
    Zugriff dann mit 
    
    test.A.wert1 oder test.B.wert1 auf den Inhalt von wert1  Beide Zugriffe sind absolut gleichbereichtigt
    Auf test.B.wert4 kann nicht über test.A. Element zugegriffen werden.
    

    😉 😉

    PS: Dieser Ansatz ist mir erst jetzt eingefallen, er ist IMHO standardkonform und kompilerunabhängig.



  • @wischmop2

    Es könnte ja sein, dass der Compiler die Variablen von foo1 in einer ganz anderen Reihenfolge anordnet als die Variablen von foo2.

    ich weis jetzt nicht wirklich genau was der C-Standard dazu sagt,
    aber da ich solche Cast's schon oft gesehen und auch benutzt habe,
    und auch keine Probleme damit hatte und auch nicht von Problemen gehört habe,
    schliesse ich es aus, das der Compiler etwas an der Reihenfolge verändert

    das Alignment der Strukturelemente bestimmt der Compiler bzw. die Compilersettings und dieses kann zu Padbytes führen, aber dies macht er ja dann auch bei den Strukturen, mit den zusätzlichen Elementen, in gleicher Art und Weise, so das da wieder eine Übereinstimmung vorhanden ist

    @PAD
    ausser das ich bei dieser Version für mich keinen klaren Vorteil sehe,
    besteht wohl das Problem, das dann ausserhalb der Bibliothek auch wieder die prvaten sachen bekannt sind, was wischmop2 wie es scheint nicht möchte



  • Inzwischen habe ich auch Code gesehen, wo so etwas gemacht wird. Und zwar bei der Netzwerk-Programmierung unter Linux (ist aber imho bei Win32 sogar der selbe Code). Wenn man sich einen (TCP-)Server schreibt, definiert man in einer Variablen vom Typ "struct sockaddr_in" u.a. den Port auf dem zu Lauschen ist etc., später beim Bind-Aufruf wird das Ding in ein "allgemeineres struct sockaddr" gecastet.

    Gut, ich gehe nun auch durch eure Beiträge mit Sicherheit davon aus, dass es damit nie Probleme geben wird. Und nochmals danke für eure Unterstützung!


Anmelden zum Antworten