Methode um (zB) unsigned int in bytes aufzuspalten



  • Hallo, sich suche nach einer möglichst protablen Methode, eine unsigned (!) POD Variable in bytes zu spalten. Mit portabel meine ich, dass die methode unabhängig von Big/Litte Endian, Größe eines Bytes (7 oder 8 bit) und größe der unsigned variable ist.

    Mein erster Ansatz sieht so aus. Allerdings weis ich nicht, ob das wegen Litte/Big Endian portabel ist:

    typedef unsigned int var;
    typedef unsigned char byte;
    
    var zahl = /*irgend ne zahl*/;
    for(unsigned int a=0;a!=sizeof(var);++a)
    {
        byte tmp = static_cast<byte>( zahl >> a*CHAR_BIT );
        //mit tmp arbeiten
    }
    

    Mein zweiter Ansatz sähe so aus:

    //typedefs wie oben
    
    var *p = &zahl;
    for(unsigned int a=0;a!=sizeof(var);++a)
    {
       byte* tmp = reinterpret_cast<byte>(p)+a;
       //mit tmp arbeiten
    }
    

    Ist das so richtig? Ist das portabel? Welche Viante würdet ihr wählen?



  • Formulier das mal genauer, was Du erreichen willst. Du kannst natürlich einfach memcpy benutzen und sizeof(unsigned) Bytes kopieren. Das klappt dann auch solange du den Rechner nicht zwischendurch wechselst. Was Du genau unter "portabel" verstehst, weiß ich in dem Kontext nicht. Willst Du es "portabel genug" haben, dass wenn Du das Ding in eine Datei schreibst, damit zu einem anderen Rechner gehst, und wieder einlädst noch dieselbe Zahl rauskommt? (Das wäre dann ein portables Dateiformat) Wenn ja, kannst Du die Zahl manuell "kodieren" (encoding), also zB in Oktette über >> und & zerlegen und wieder genauso über << und | zusammenbauen.


  • Mod

    Ich würde wie mein Vorposter zwar auch gerne wissen, was das werden soll, aber ich kann dir auf jeden Fall einen sehr allgemeinen Ansatz geben:

    Der kleinste Datentyp ist char und entspricht in 99,999% der Fälle einem Byte. Dessen Größe kannst du mittels numeric_limits herausfinden. Und dann einfach ausrechnen, wie man deine Werte mit Bytes dieser Größe darstellt.



  • Ja, ich hatte mich vielleicht ein wenig schwammig ausgedrückt. Ich bin relativ neu auf dem Gebiet (maschinennahme Programmierung) 🙂

    Was ich mit portabel meine ist Folgendes: Die Methode soll mit jedem Compiler funktionieren. Egal ob die Bytegröße 7 oder 8 Bit ist, er Little- oder Big Endian benutzt oder wie groß zB unsigned int ist (32 oder 64 bit).
    Das heißt, wenn ich zB die bytes binär in eine Datei schreibe, soll die konvertierung auf dem selben PC, mit dem gleichen Compiler kompilierten Programm funktionieren. D.h. ein Transfer von PC zu PC oder Kompiler zu Kompiler ist nicht vorgesehen.

    Ich hoffe ihr verseht das. Keine Ahnung wie ich das besser ausdrücken sollte. 🙂
    Sonst freue ich mich auch über andere Ansätze, wie ich ein (zB) unsigned int in bytes spalten kann.



  • Bits&Bytes schrieb:

    Das heißt, wenn ich zB die bytes binär in eine Datei schreibe, soll die konvertierung auf dem selben PC, mit dem gleichen Compiler kompilierten Programm funktionieren. D.h. ein Transfer von PC zu PC oder Kompiler zu Kompiler ist nicht vorgesehen.

    Dann kannst Du das ganz einfach per memcpy und sizeof erledigen. Das Dateiformat ist dann aber nicht portabel.



  • TGGC schrieb:

    Eine union aus char[sizeof(int)] und int machen.

    Nein. Nicht, wenn der Code portabel sein soll. Der GCC bietet das als Zusatz-Feature an. Es führt aber strenggenommen zu undefiniertem Verhalten, wenn Du ein anderes union-Element ausliest, als das, welches Du zuletzt überschrieben hast.



  • Also so?

    typedef unsigned int  var;
    typedef unsigned char byte;
    
    var *p = &zahl;
    for(unsigned int a=0;a!=sizeof(var);++a)
    {
        byte* tmp;
        memcpy( tmp, p+a, 1 );
    }
    

    oder so?

    var *p = &zahl;
    for(unsigned int a=0;a!=sizeof(var);++a)
    {
        byte* tmp = new byte[sizeof(var)];
        memcpy( tmp, p, sizeof(var) );
    }
    


  • Edit:
    das 2. ist natürlich Schwachsinn. Sollte so heißen (ohne die Schleife)

    byte* tmp = new byte[sizeof(var)];
    memcpy( tmp, p, sizeof(var) );
    


  • TGGC schrieb:

    Welcher Teil davon ist denn nicht portabel?

    Das ist genau meine Frage 😃



  • unsigned t = 31415;
    char x [sizeof(t)];
    std::memcpy(&x,&t,sizeof(x)); // x <-- t
    ...
    std::memcpy(&t,&x,sizeof(t)); // t <-- x
    

    ...kannst aber auch so etwas machen:

    int dings = 33;
    my_ostream.write(reinterpret_cast<char*>(&dings),sizeof(dings));
    

    Aber wie gesagt: Das "Dateiformat" ist dann plattformabhängig, also nicht portabel zwischen versch Architekturen.



  • TGGC schrieb:

    Welcher Teil davon ist denn nicht portabel?

    int main() {
      union {
        int dings;
        char bums[sizeof(int)];
      } u;
      u.dings = 99;
      int q = u.bums[0]; // U.B.
    }
    

    Wenn Du zuletzt dings überschrieben hast, darfst Du nur dings wieder auslesen, nichts anderes. Das sind die strengen Regeln des Standards.

    Gruß,
    SP



  • Also wenns portabel sein soll, dass könnte das hier was sein:

    unsigned int mask = (1 << (CHAR_BIT)) - 1;
    
    unsigned char bytes[sizeof(unsigned int)];
    
    unsigned int val = 0xAABBCCDD;
    
    for(size_t n = 0; n != sizeof(unsigned int); ++n)
    {
        bytes[n] = (val >> (n * CHAR_BIT)) & mask;
    }
    

    Das kann man dann noch schön in einer Funktion verpacken...



  • Die Methode von Sebastian Pizer über memcpy ist glaube ich genau das, wonach ich suche. Danke 😃

    Aber memcpy liegt nicht im namespace std, oder?



  • Bits&Bytes schrieb:

    Aber memcpy liegt nicht im namespace std, oder?

    Mit #include <cstring> schon.



  • Bits&Bytes schrieb:

    Die Methode von Sebastian Pizer über memcpy ist glaube ich genau das, wonach ich suche. Danke 😃

    Aber memcpy liegt nicht im namespace std, oder?

    memcopy schert sich aber nicht um Endianess.



  • std::memcpy(&x,&t,sizeof(x)); // x <-- t 
    std::memcpy(&t,&x,sizeof(t)); // t <-- x
    

    Aber dieser Code sollte doch funtkionieren, egal, ob Litte-/oder Bigendian, solange man auf dem selben PC/selben Compiler bleibt. Richtig?



  • Bits&Bytes schrieb:

    std::memcpy(&x,&t,sizeof(x)); // x <-- t 
    std::memcpy(&t,&x,sizeof(t)); // t <-- x
    

    Aber dieser Code sollte doch funtkionieren, egal, ob Litte-/oder Bigendian, solange man auf dem selben PC/selben Compiler bleibt. Richtig?

    Ja, das schon.



  • Gut. Das reicht mir erstmal. Ist nur für ein kleines Hobbyprojekt, um mehr über maschinennahe Programmierung zu lernen 🙂



  • TGGC schrieb:

    Die strengen Regel des Standards garantieren aber auch, das genau das gleiche wie bei diesem memcpy passiert, da die member die gleiche Adresse haben muessen und sich so verhalten wie einzelne Strukturen.

    "garantieren" was genau? Bitte auch mit Zitaten belegen. Danke!


  • Mod

    Sebastian Pizer schrieb:

    Wenn Du zuletzt dings überschrieben hast, darfst Du nur dings wieder auslesen, nichts anderes. Das sind die strengen Regeln des Standards.

    Nicht ganz. Der Standard garantiert, das stets nur derjenige Wert im union existiert, der zuletzt hineingeschrieben wurde (9.5/1). Nichtsdestotrotz existieren natürlich alle nicht-statischen Member, solange das union-Objekt als Ganzes existiert, da es sich um PODs handelt (3.8/2) - die anderen Member haben dann eben nur keinen gespeicherten Wert.

    Der Zugriff auf den gespeicherten Wert eines Objektes per lvalue des Typs char oder unsigned char ist stets zulässig (3.10/15 i.V.m 3.9/4 - 3.9/15 sagt eigentlich nur, wann ein Zugriff garantiert zu UB führt, wir brauchen 3.9/4, um herauszubekommen, was das Ergebnis sein soll). Somit ist diese Funktion völlig legal

    template <typename T>
    std::ostream& binary_write(std::ostream& s, const T& x)
    {
        return s.write( &reinterpret_cast<const char&>( x ), sizeof x );
    }
    

    Der schreibende Zugriff auf ein beliebiges Objekt per lvalue eines beliebigen Typs ist sowieso immer legal, im Regelfall hört das ursprüngliche Objekt dann allerdings auf zu leben (3.8/1 Alt. "reuse"), was unter bestimmten Umständen zu UB führt (3.8/4).

    Im speziellen Fall des Schreibens per lvalue der Typen char oder unsigned char endet die Lebenszeit des zuvor dort existierenden Objektes nicht zwangsläufig, nämlich dann nicht, wenn der geschriebene Wert selbst vom Typ des zuvor dort existierenden Objektes ist. Andernfalls entsteht dort ein Objekt des Typs, dessen Wert dort geschrieben wurde, sofern es sich dabei um eine POD handelt. (Weil es statisch i.d.R. schwer zu beweisen ist, das der Typ des geschriebenen Objekts nicht der eines bestimmten Typs ist, ist es praktisch in der Regel bedenkenlos möglich, beim folgenden Lesevorgang einen beliebigen POD-Typ zu nehmen, solange das geschriebene Bitmuster hierfür sinnvoll ist, die folgenden Lesevorgänge müssen dann aber nat. konsistent sein). (3.9/4 i.V.m. 3.9/2).
    Andernfalls (der geschriebene Wert gehört zu keinem anderen POD-Typ) entsteht an dieser Stelle als letzter Ausweg ein Array aus (unsigned) char - denn irgenwas muss dort ja sein.
    Folglich können wir so etwas machen:

    template <typename POD>
    std::istream& binary_read(std::istream& s, POD& x)
    {
        return s.read( &reinterpret_cast<char&>( x ), sizeof x );
    }
    

    Zweckmäßigerweise würde man die Beschränkung auf POD auf die binary_write-Funktion übertragen, weil andere Typen zwar geschrieben, aber unmöglich so wieder hergestellt werden können.
    Der C99-Standatrd ist etwas ausführlicher in 6.5/6 ff. wenn es um das geht, was der C-Standard als "effective type" definiert. Das kann als Interpretationshilfe dienen.


Anmelden zum Antworten