Methode um (zB) unsigned int in bytes aufzuspalten


  • 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.



  • Ja, 3.10/15 war mir eigentlich bekannt. Ich hatte im union -Kontext einfach nicht an den char/unsigned char Fall gedacht, sondern mich nur an all die anscheinend eher pauschalen Auslegungen erinnert, die man überall finden kann. Beispielsweise vom 22.1.2009 aus comp.lang.c++:

    James Kanze schrieb:

    union
     {
       long l;
       bytes b[4]; 
     } u;
    

    That's formally worse, since it results in undefined behavior if you access b when the last value was written to l. (In practice, which is better depends on the compiler---some compilers ignore aliasing which results from a reinterpret_cast.)

    Man findet Aussagen in dieser Richtung en masse.

    Aber ich muss schon sagen, dass Deine Ausführung "schwer verdaulich" ist. Ich werde mir 3.9 und 3.10 später nochmal anschauen...

    Gruß,
    SP


  • Mod

    Sebastian Pizer schrieb:

    Aber ich muss schon sagen, dass Deine Ausführung "schwer verdaulich" ist. Ich werde mir 3.9 und 3.10 später nochmal anschauen...

    Das verwundert nicht. Du wirst es auch sonst nirgends so aufgeschrieben finden.

    Sebastian Pizer schrieb:

    Beispielsweise vom 22.1.2009 aus comp.lang.c++:

    James Kanze schrieb:

    union
     {
       long l;
       bytes b[4]; 
     } u;
    

    That's formally worse, since it results in undefined behavior if you access b when the last value was written to l. (In practice, which is better depends on the compiler---some compilers ignore aliasing which results from a reinterpret_cast.)

    3.10/15 interessiert nicht, wiedas lvalue, mit dem man den Zugriff durchführt, zustande kommt. Es existiert auch nirgendwo sonst im Standard eine Stelle, die hier irgendwelche Sonderregeln für unions aufstellt. 9.5 sagt etwas über den gespeicherten Wert, wie Zugriffe darauf zu erfolgen haben, entspricht aber den ganz normalen Regeln (abgesehen von dem "common initial sequence" Fall).

    Wo union vs. reinterpret_cast ins Spiel kommt, sind offenbar bestimmt Compilererweiterungen des gcc (und evtl. anderer Compiler). Diese Erweiterungen definieren das Ergbnis von Zugriffen, die nach 3.10/15 eigentlich UB sind, wenn das betreffende lvalue aus einem solchen union-cast hervorgegenagen ist. Das ist selbstverständlich zulässig, nur darf es nicht den Blick auf das, was der Standard verspricht, verstellen. Diese Erweiterung wird ja unter anderem im Linuxkernel oft eingesetzt, man kann also auch davon ausgehen, dass sie erhalten bleibt.


Anmelden zum Antworten