Zwei char in ein short "schieben": Unterschiedliche Ergebnisse



  • Hey...
    Ich möchte mehr in Grundlagen verstehen und hab jetzt das probiert:

    union MyUnion
    {
    	unsigned char b1;
    	unsigned char b2;
    
    	unsigned short word;
    }; MyUnion globalMyUnion;
    
    struct MyStruct
    {
    	unsigned short word;
    }; MyStruct *pMyStruct;
    
    int main()
    {
    	// TEST 1 = 33
    	globalMyUnion.b1 = 102;
    	globalMyUnion.b2 = 33;
    	cout << globalMyUnion.word << endl;
    
    	// TEST 2 = 52257
    	MyUnion myUnion;
    	myUnion.b1 = 102;
    	myUnion.b2 = 33;
    	cout << myUnion.word << endl;
    
    	// TEST 3 = 8550
    	unsigned char buf[2] = { 102, 33 };
    	unsigned char* pBuf = buf;
    	pMyStruct = (MyStruct*)pBuf; 
    	cout << pMyStruct->word << endl;
    }
    

    Warum sind die Ergebnisse so unterschiedlich? Was passiert bei den einzelnen Tests genau?

    Danke!
    MfG



  • Da die Tests 1 und 2 undefiniert sind, lohnt es nicht, über deren Ergebnisse zu spekulieren. Du darfst von einer Union immer nur das jeweils aktuell gültige (sprich, das zuletzt gesetzte) Element ansprechen.

    Beim dritten Test kommt ein zu erwartendes Ergebnis raus. Im Speicher liegen die Zahlen (Hex 0x66 und 0x21), welche, da Du vermutlich auf einer Little-Endian Plattform arbeitest, in umgekehrter Reihenfolge interpretiert werden. 0x2166 Hex entspricht 8550.


  • Mod

    Die Fälle 2 und 3 sind undefiniert, Fall 1 hat möglicherweise definiertes Verhalten - das muss ich noch mal nachlesen.



  • Wäre Fall 3 denn definiert, wenn garantiert wäre, dass der Array (für MyStruct) korrekt ausgerichtet ist?



  • Es geht mir nämlich darum, die zwei Bytes 102 und 33 auf einen Wert zu bekommen.
    Wie soll ich das denn sonst machen?

    Danke!
    MfG



  • ceplusplus@loggedoff schrieb:

    Es geht mir nämlich darum, die zwei Bytes 102 und 33 auf einen Wert zu bekommen.
    Wie soll ich das denn sonst machen?

    Am besten mit legitimen, definierten, plattformunabhängigen Mitteln. 🙂

    unsigned char bytes[...];
    
    // extrahiere Bytes 1 und 2, die eine Big-Endian-Repräsentation einer vorzeichenlosen 16-Bit Zahl enthalten
    uint16_t word = ( bytes[0] << 8 ) | bytes[1];
    


  • Wenn dann müsste die union so aussehen

    union MyUnion
    {
        struct tagName { // Name?
        unsigned char b1;
        unsigned char b2;
        } twochar; // Name?
    
        unsigned short word;
    }; MyUnion globalMyUnion;
    


  • Aha, danke erstmal.
    Hat es etwas mit dem sogenannten "Padding" zu tun, dass Fall 3 undefiniert ist?

    Was mache ich aber, wenn ich ein Array hunderter Bytes habe, bestimmte Bytes aber eben zusammengehören also zB. intern einen float repräsentieren und ich eine entsprechende Struktur definieren möchte?

    (Ich muss ein unbekanntes Protokoll auswerten)

    MfG


  • Mod

    Noch mal nachgelesen. Alle 3 Fälle sind undefiniert, Fall 3 auch unabhäng von Alignmentvoraussetzungen (die kommen noch hinzu). Interessant ist dagegen der Fall, wenn word der erste Member des Unions wäre. Wenn wir annehmen, dass in unserer Implementation Objekt- und Wertrepräsentation von short identisch sind (es existieren also keine Paddingbits oder Traprepräsentationen), dann wäre Fall 1 wohldefiniert (das konkrete Ergebnis aber von der Implementation abhängt), Fall2 unspezifiziert (wegen fehlender Initialisierung) oder wohldefiniert (falls sizeof(char)==sizeof(short) ).

    Der Zugriff auf ein Objekt über Ausdrücke mit Zeichentyp ändert nicht den Typ des Objekts, insofern muss die Konkurrenz zur Regel, dass in einem Union nur ein Wert gleichzeitig aktiv sein kann, zuungunsten dieser Regel aufgelöst werden - andernfalls wären elementare Zugriffe etwa per memcpy auf solche Objekte problematisch.

    Referenzen (C++): 3.9/2-4;3.10/15;8.5/1;9.5/1



  • camper schrieb:

    Interessant ist dagegen der Fall, wenn word der erste Member des Unions wäre. Wenn wir annehmen, dass in unserer Implementation Objekt- und Wertrepräsentation von short identisch sind (es existieren also keine Paddingbits oder Traprepräsentationen), dann wäre Fall 1 wohldefiniert (das konkrete Ergebnis aber von der Implementation abhängt), Fall2 unspezifiziert (wegen fehlender Initialisierung) oder wohldefiniert (falls sizeof(char)==sizeof(short) ).

    meines wissens nach sind nur zugriffe auf unions wohldefiniert, die einen Wert in der Form auslesen, wie er eingelesen wurde. Sprich, wenn ein word eingelesen wurde, muss ein word ausgelesen werden, nicht ein char oder was auch immer. Das ist ja das große Problem bei unions, dass all diese schönen sachen komplett undefiniert sind.


  • Mod

    otze schrieb:

    meines wissens nach sind nur zugriffe auf unions wohldefiniert, die einen Wert in der Form auslesen, wie er eingelesen wurde. Sprich, wenn ein word eingelesen wurde, muss ein word ausgelesen werden, nicht ein char oder was auch immer. Das ist ja das große Problem bei unions, dass all diese schönen sachen komplett undefiniert sind.

    Das steht aber so nirgendwo im Standard und ich denke, man kann es auch nicht durch Auslegung herausbekommen.

    ISO 14882:2003 schrieb:

    9.5 Unions [class.union]
    1 In a union, at most one of the data members can be active at any time, that is, the value of at most one of the data members can be stored in a union at any time. [Note: one special guarantee is made in order to simplify the use of unions: If a POD-union contains several POD-structs that share a common initial sequence (9.2), and if an object of this POD-union type contains one of the POD-structs, it is permitted to inspect the common initial sequence of any of POD-struct members; see 9.2. ] The size of a union is sufficient to contain the largest of its data members. Each data member is allocated as if it were the sole member of a struct.

    Dort steht nichts dazu, wie man auf den gespeicherten Wert zugreifen kann (da steht zwar in dem Verweis etwas von "inspect", nicht aber wie dies zu geschehen hat). Um das herauszufinden, müssen wir an einer ganz anderen Stelle suchen:

    ISO 14882:2003 schrieb:

    3.10/15
    If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined48):
    — the dynamic type of the object,
    — a cv-qualified version of the dynamic type of the object,
    — a type that is the signed or unsigned type corresponding to the dynamic type of the object,
    — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
    — an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
    — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
    — a char or unsigned char type.

    Das ist zunächst einmal eine Regel, die uns sagt, wann etwas nicht definiert ist. Dafür müssen wir wiederum woanders schauen - uns interessieren hier nur char&co., dafür muss man 3.9/4 heranziehen. Betrachten wir das Ganze jetzt aus dem Blickwinkel von union-Membern wird plötzlich klar, warum dort diese merkwürdige Regel des nur-eine-Wert-gleichzeitig-aktiv steht; ohne diese Regel wüssten wir nicht, auf welches Objekt wir 3.10/15 anzuwenden haben. 9.5/1 ist also keine Einschränkung sondern macht das sinnvolle Arbeiten mit Unions überhaupt erst möglich.

    Ein bisschen schwieriger ist noch der exotische Fall, falls unser Ausdruck ein rvalue ist. Etwa

    union foo
    {
        char i;
        char a[1];
        double d;
    };
    foo f() { foo x; x.d = 1.0; return x; }
    int main()
    {
        cout << f().d; // was nun?
        cout << f().i; // und hier? im Vergleich zu
        cout << f().a[0]; // hier?  Dies wäre in C übrigens ill-formed
    }
    

    Uns ist natürlich klar, was bei f().d passiert - es ist aber nicht ganz einfach, dies zu begründen; glücklicherweise sind diese Fälle relativ selten.


Anmelden zum Antworten