Arrays mit 0 Einträgen, warum verboten?



  • Hi,

    kann mir jemand sagen, warum Arrays mit 0 Einträgen verboten sind?
    Ich bin gerade dabei, mein Netzwerksystem neu zu schreiben und bin jetzt grade dabei, ein Paket zu schreiben.

    Bisher sieht das so aus: (es ist nur eine struct, um noch deutlicher herauszuheben, dass es nur Nutzdaten sind und keinerlei Methoden oder sonstwas, ja, ich weiß, das ist in C++ auch bei Strukturen möglich, aber egal...)

    struct UDPPacket
    {
    	unsigned long m_lMessageID;
    	unsigned short m_shMessageType;
    	unsigned short m_shSize;
    	char m_data[0];
    };
    

    Allokieren wollte ich jetzt einfach so:
    UDPPacket* pkt = reinterpret_cast<UDPPacket*>(new char[sizeof(UDPPacket) + dataSize]);

    Soweit ich das beurteilen kann, funktioniert es auch (ist natürlich nicht typsicher und nicht gegen Überschreiben des zugewiesenen Bereichs geschützt, aber das ist an der Stelle im Code egal, da die UDPPackets nie direkt, sondern immer nur über eine Proxyklasse, Packet, bearbeitet werden, die dann auch gleich die Byte Order zum Versenden konvertiert usw.).

    Nur gibt's eine Warnung:

    warning C4200: Nicht dem Standard entsprechende Erweiterung: Null-Array in Struktur/Union

    Warum ist das nicht erlaubt? Ist doch eigentlich so ein praktisches Feature für so Fälle wie den hier.

    ChrisM



  • hmm.. ich würde mal sagen: weil arrays immer (eigentlich) zeiger auf das erste element sind. und wenn es kein erstes element gibt, gehts nicht. weiß es aber auch nicht genau.
    geloescht



  • du musst das folgende machen:

    struct UDPPacket
    {
    	unsigned long m_lMessageID;
    	unsigned short m_shMessageType;
    	unsigned short m_shSize;
    	char m_data[1];
    };
    

    ps: so eine art von code findet man unter anderem in john carmack's quake code



  • Hi,

    ja, aber dann hab ich ja immer mindestens ein Datenbit, was ja durchaus nicht immer erwünscht ist, z.B. bei Keep Connection Alive Messages, die ja einfach nur Größe 0, ID und Messagetyp (ist dann MSGKEEPALIVE) haben.

    ChrisM



  • Ich denke, es liegt daran, dass Objekttypen immer eine Größe ungleich 0 haben müssen.



  • Hi,

    schade. 😞

    Was würdet ihr dann machen?
    - Daten woanders speichern und Zeiger darauf erstellen (Nachteil: Ich kann das Packet nicht mehr so wie es ist verschicken und muss pro Paket zweimal Speicher allokieren, bei einem großen Server mit vielen kleinen Paketen macht das schon was aus)
    - Warnung ausschalten 😃

    ChrisM



  • ChrisM schrieb:

    Hi,

    ja, aber dann hab ich ja immer mindestens ein Datenbit,

    Schlimmer: Es ist ein Byte.

    Entweder Du schreibst es also "ordentlich", oder lebst weiter mit der Warnung.

    (In C hat für sowas eine eigene Schreibweise erfunden.)



  • Hi,

    ja, ich meine doch ein Byte, halt ein char.

    Ich schalte die Warnung jetzt einfach aus und überlade Kopierkonstruktor und =-Operator (die Standardversionen der beiden funktionieren mit meiner variablen Größe logischerweise nicht), wenn hier niemand mehr eine andere Lösung einfällt... 🙂

    ChrisM



  • Spricht was gegen

    struct UDPPacket 
    { 
    	unsigned long m_lMessageID; 
    	unsigned short m_shMessageType; 
    	unsigned short m_shSize; 
    	char **m_data; 
    };
    

    ?



  • Das wär die erste Lösung, die er genannt hat. Nachteil: Doppelte Allokation, komplizierteres Einlesen.



  • wie wärs mit 2 Structs einem mit m_data und einem ohne ?

    Devil



  • Bashar schrieb:

    Das wär die erste Lösung, die er genannt hat. Nachteil: Doppelte Allokation, komplizierteres Einlesen.

    Ich sehe das so, das er ne Struktur hat und die Variable missbraucht um den Anfang eines beliebig grossen blocks zu bestimmen.
    Bei allen anderen dingen komme ich doch wieder auf doppelte Allocation.



  • ich würde alles über methoden machen
    z.b.

    class UDPPacket
    {
        unsigned long messageId() const
        {
            return *reinterpret_cast<unsigned long*>( this );
        }
    
        unsigned short messageType() const
        {
            return *reinterpret_cast<unsigned short*>( static_cast<char *>( this ) + 4 );
        }
    
        unsigned short size() const
        {
            return *reinterpret_cast<unsigned short*>( static_cast<char *>( this ) + 6 );
        }
    
        void * data()
        {
            return static_cast<char *>( this ) + 8;
        }
    };
    

    man müsste aber noch überlegen wie man das ganze leichter warbar macht, denn bei änderungen der layout muss man viele zahlen ändern



  • Hi,

    @Knueddelbär: Nein, die Länge des Headers ist ja vorgegeben, nur die Länge des Datenpakets nicht.

    @Gerard: Sorry, aber was soll das bringen?

    Danke für eure Antworten! 🙂

    ChrisM



  • ChrisM schrieb:

    Hi,

    @Knueddelbär: Nein, die Länge des Headers ist ja vorgegeben, nur die Länge des Datenpakets nicht.

    UDPPacket* pkt = reinterpret_cast<UDPPacket*>(new char[sizeof(UDPPacket) + dataSize]);

    Für was brauchst Du denn die Variable m_data wenn nicht für die Anfangsadresse des Speichers ? Im moment macht das für mich keinerlei sinn



  • IIRC muß ein Datenelement immer sizeof() != 0 haben (IIRC^2 weil der Stnadard eine begrenzte Adreßeindeutigkeit garantiert - aber hier wird's schon arg duster mit den Erinnerungen)

    Warum allozierst du nicht nur den Header für "datenfreie" Pakete? Ob und wieviel Daten da sind, mußt du ja eh aus dem header rausbekommen.

    Das offset von m-data bekommst du (getrickst) mit

    (char *) &(((UDPPacket *)0)->m_data) - (char *)((UDPPacket *)0)
    

    ist nicht ganz legal (eigentlich darfst du nen NULL-Zeiger nicht dereferenzieren), aber rechnet ja eh der Compiler aus 🙄

    Legal kannst aber auch einfach (in anlehnung an gerhard)

    #pragma pack(push,1) // byte-alignment - compiler-abhängig!
    struct UDPPacket 
    { 
        unsigned long m_lMessageID; 
        unsigned short m_shMessageType; 
        unsigned short m_shSize; 
        char *   data() { return ((char *)this) + sizeof(*this); }
    };
    #pragma pack(pop)
    

    [/code]

    da kannst du gut noch ein _ASSERT einfügen, ob die entsperchende Message überhaupt daten haben darf. Ist m.E. ein guter kompromiß.



  • Hi,

    OK, danke, die Lösung gefällt mir. 😉

    ChrisM



  • ich habe mal wegen dieser warnung nachgelesen,
    das mit dem [0] ist eine erweiterung von microsoft, die wurde mal vor jahren eingeführt,
    wenn man die microsoft extensions mit dem kompilerschalter /Za ausschaltet bekommt man eine fehlermeldung, aber mit der extension (/Ze), welche auch std.mässig aktiviert ist, bekomme ich jenach kompilerversion keine meldung oder nur diese warnung, das es sich eben um eine extension handelt,
    ich habe auch mal den metrowerks codewarrior probiert, und der kommt auch damit klar, ohne eine warnung,
    also kannst du sicher diese variante ohne probleme benutzen, du solltest nur prüfen, ob die von dir verwendeten kompiler sie unterstützen



  • bozo schrieb:

    ich habe mal wegen dieser warnung nachgelesen,
    das mit dem [0] ist eine erweiterung von microsoft, die wurde mal vor jahren eingeführt

    Hehe klar, und Tolkien hat die Orks von DSA geklaut.



  • @Bashar
    dann schau doch in die msdn, es sieht dort ganz danach aus

    hier noch was aus der msdn:

    The given member in the structure or union contains an array that does not have a subscript or that has a zero subscript. This kind of array is legal only as the last member of a structure or union.

    also geht auch folgendes:

    struct UDPPacket 
    { 
        unsigned long m_lMessageID; 
        unsigned short m_shMessageType; 
        unsigned short m_shSize; 
        char m_data[]; 
    };
    

    habe es mit vs und cw getestet, es geht


Anmelden zum Antworten