Methode um (zB) unsigned int in bytes aufzuspalten



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



  • TGGC schrieb:

    Ich hab nicht wirklich Lust mit irgendwelchen Zitaten rumzuschmeissen, wenn dich das so interessiert, dann suche sie selbst.

    Habe ich ja. 3.10/15 erlaubt Dir, auf PODs über lvalues vom Typ char und unsigned char zuzugreifen. Allerdings sehe ich im Moment nicht, dass

    unsigned foo(float x) {
       union {
         float fl;    // nix char, nix unsigned char
         unsigned uu; // nix char, nix unsigned char
       };
       fl = x;
       return uu;
     }
    

    funktionieren muss, auch wenn sichergestellt worden ist, dass die Größe von float und unsigned übereinstimmt und dass jedes float Bitmuster ein legales unsigned Bitmuster ergibt. Falls es wirklich so ist, wie viele behaupten (also, dass Du auf nur auf ein union-Element lesend drauf zugreifen darfst, welches zuletzt "beschrieben" worden ist), könnte der Compiler die Zeile "fl = x" wegoptimieren, weil fl danach nie wieder "angefasst" wird. Von daher ist es schon relativ wichtig, was der Standard erlaubt und was nicht.

    Dagegen sehe ich bei

    unsigned foo(float x) {
       unsigned u;
       memcpy(&u,&x,sizeof(u));
       return u;
     }
    

    keine Probleme. Die Regeln in 3.10/15 werden auch nicht verletzt, soweit ich das beurteilen kann.

    TGGC schrieb:

    Mag sein, das du theoretisch Recht hast.

    Ich bin mir da ja selbst gar nicht mehr so sicher -- bin kein "language lawyer".

    TGGC schrieb:

    Und alle diese Member haben die selber Adresse. Und darum hat die union genau den gleichen Effekt, als wenn du einfach den Speicher einer strcut einfach an die Adresse eine anderen struct kopierst, wenn beide structs gleich gross sind.

    Das "und darum" überzeugt mich nicht.

    Gruß,
    SP


  • Mod

    TGGC schrieb:

    In der Praxis zaehlt sowieso nicht, was der Standard vorschreibt, sondern wie es gerade ist.

    In Praxis wird i.d.R. mehr zulässig sein, als dem Standard zufolge zulässig ist. Nur wie viel mehr ist nicht konstant sondern wird weniger (man bedenke z.B. wie viel Code durch die jüngsten Versionen des gcc kaputtgegangen ist, weil striktere Aliasregeln durchgesetzt wurden). Deshalb ist es wichtig, den Standard zu kennen als etwas, worauf man sich verlassen darf.

    @Sebastian Pitzer: Die Einschätzung zu den Beispielen teile ich.
    Möglicherweise geht auch das hier:

    unsigned foo(float x) {
       memcpy(&x,&x,sizeof(x));
       return reinterpret_cast<unsigned&>(x);
     }
    

    Der Funktionsaufruf beim memcpy kann trivialerweise wegoptimiert werden, er müsste allerdings trotzdem für die nötige Verschleierung des Typs des gespeicherten Wertes sorgen. Eine abschließende Meinung habe ich mir dazu aber noch nicht gebildet.


Anmelden zum Antworten