Ein paar Fragen zu memcpy und versch. Datentypen



  • @1: memcpy() kopiert byteweise den kompletten Speicherbereich - da du keine "externen" Daten verwendest, ist das hier völlig ausreichend (aber unnötig - du kannst auch read()/write() direkt auf deine FILE_HEADER loslassen).

    @1b: "#pragma pack()" gibt an, wie die Strukturelemente im Speicher ausgerichtet werden. Je nach Einstellung (und Größe der Member) können noch Füll-Bytes dazwischengeschoben werden, um die Adressen zu korrigieren. Das wird nur dann problematisch, wenn du die Daten zwischen verschiedenen Systemen austauschen willst.

    @2: Wenn alle Teile des Programms vom selben Compiler stammen, passt auch die Bitlage deiner Zahlenwerte zusammen. Wenn du auf diesem Weg Daten zwischen verschiedenen Systemen austauschen willst, ist die Anpassung über Bit-Operationen sicherer (btw, das gilt auch für die int-Anteile aus Teil [1]).



  • templäd schrieb:

    Teilantworten:
    Also bei Deiner Datenstruktur sollte pack(1) keinen Einfluss habe, da Deine Addressen alle durch 4 (defaultwert) teilbar sind.
    Generell muss pack beim schreiben und lesen einfach gleich sein.

    Also das alles durch 4 teilbar ist, ist glaube ich Zufall. Würde folgende abgeänderte Struktur an Funktionalität etwas ändern?

    template<class CharType>
    struct FILE_HEADER
    {
        CharType  m_Signature[23]; // vorher 20
        int       m_Value1;
        int       m_Value2;
        CharType  m_Data[111]; // vorher 100
        size_t    m_DataLen;
    };
    

    templäd schrieb:

    Deine Version von ulong_to_uchar ist plattform bzw little-big-endian unabhängig also "besser" als memcopy aber langsamer(falls du es sehrsehrsehr oft aufrufst)

    Also entnehme ich darus das es in meinem Fall egal ist. Die Software soll nur unter Windows laufen auf Intel- bzw. IBM kompatiblen Systemen.

    CStoll schrieb:

    @1: memcpy() kopiert byteweise den kompletten Speicherbereich - da du keine "externen" Daten verwendest, ist das hier völlig ausreichend (aber unnötig - du kannst auch read()/write() direkt auf deine FILE_HEADER loslassen).

    Müsste dann nur in CharType casten also

    std::basic_fstream<CharType> file("Dateiname");
    
    file.write(reinterpret_cast<CharType*>(&Header), sizeof(FILE_HEADER<CharType>));
    

    Wäre um einiges performater, da ich eine Speicheranforderung weglassen kann.

    CStoll schrieb:

    @1b: "#pragma pack()" gibt an, wie die Strukturelemente im Speicher ausgerichtet werden. Je nach Einstellung (und Größe der Member) können noch Füll-Bytes dazwischengeschoben werden, um die Adressen zu korrigieren. Das wird nur dann problematisch, wenn du die Daten zwischen verschiedenen Systemen austauschen willst.

    Wie oben erwähnt. Die Software soll nur unter Windowssystemen laufen. Hättest du vielleicht ein Beispiel wo #pragma pack(xyz) genutzt werden müsste?

    CStoll schrieb:

    @2: Wenn alle Teile des Programms vom selben Compiler stammen, passt auch die Bitlage deiner Zahlenwerte zusammen. Wenn du auf diesem Weg Daten zwischen verschiedenen Systemen austauschen willst, ist die Anpassung über Bit-Operationen sicherer (btw, das gilt auch für die int-Anteile aus Teil [1]).

    Also kein Problem solange es unter Windowssystemen läuft. Wenn es jetzt aber z.B. unter einem PowerPC wo Windows in einer VM läuft, würde es schiefgehen, richtig?



  • HaJo. schrieb:

    Also kein Problem solange es unter Windowssystemen läuft. Wenn es jetzt aber z.B. unter einem PowerPC wo Windows in einer VM läuft, würde es schiefgehen, richtig?

    ne VM sollte tunlichst das windows bit-ordering emulieren, sonst wärs keine anständige VM. probleme bekommst dann, wenn dein programm nativ auf anderen system laufen soll.



  • thordk schrieb:

    ne VM sollte tunlichst das windows bit-ordering emulieren, sonst wärs keine anständige VM. probleme bekommst dann, wenn dein programm nativ auf anderen system laufen soll.

    Stimmt. Im Nachhinein wohl schwachsinn. Danke.



  • HaJo. schrieb:

    CStoll schrieb:

    @1: memcpy() kopiert byteweise den kompletten Speicherbereich - da du keine "externen" Daten verwendest, ist das hier völlig ausreichend (aber unnötig - du kannst auch read()/write() direkt auf deine FILE_HEADER loslassen).

    Müsste dann nur in CharType casten also

    std::basic_fstream<CharType> file("Dateiname");
    
    file.write(reinterpret_cast<CharType*>(&Header), sizeof(FILE_HEADER<CharType>));
    

    Wäre um einiges performater, da ich eine Speicheranforderung weglassen kann.

    Ja, genau so sollte es funktionieren (abgesehen von der Tatsache, daß FILE_HEADER kein Template ist ;)).

    CStoll schrieb:

    @1b: "#pragma pack()" gibt an, wie die Strukturelemente im Speicher ausgerichtet werden. Je nach Einstellung (und Größe der Member) können noch Füll-Bytes dazwischengeschoben werden, um die Adressen zu korrigieren. Das wird nur dann problematisch, wenn du die Daten zwischen verschiedenen Systemen austauschen willst.

    Wie oben erwähnt. Die Software soll nur unter Windowssystemen laufen. Hättest du vielleicht ein Beispiel wo #pragma pack(xyz) genutzt werden müsste?

    Auf Anhieb nicht. Solche Anpassungen brauchst du vor allem, wenn du ein bestimmtes Speicher-Layout erzwingen willst. Bei deiner geänderten Struktur könnten z.B. nach den beiden char-Arrays padding-Bytes eingefügt werden, die (a) eventuell unnötigen Speicherplatz verbrauchen und (b) das Layout durcheinanderwürfeln können.



  • CStoll schrieb:

    Ja, genau so sollte es funktionieren (abgesehen von der Tatsache, daß FILE_HEADER kein Template ist ;)).

    Doch, FILE_HEADER ist doch ein Template. 🙂

    template<class CharType>struct FILE_HEADER { /* ... */ };
    

    CStoll schrieb:

    Auf Anhieb nicht. Solche Anpassungen brauchst du vor allem, wenn du ein bestimmtes Speicher-Layout erzwingen willst. Bei deiner geänderten Struktur könnten z.B. nach den beiden char-Arrays padding-Bytes eingefügt werden, die (a) eventuell unnötigen Speicherplatz verbrauchen und (b) das Layout durcheinanderwürfeln können.

    Schade. 🙂 Falls dir noch mal eines über den Weg läuft, wäre ich dran interessiert. Also sollte es auch noch mit der geänderten Sruktur, wo nicht mehr alle Memeber durch 4 teilbar sind klappen.



  • HaJo. schrieb:

    CStoll schrieb:

    Ja, genau so sollte es funktionieren (abgesehen von der Tatsache, daß FILE_HEADER kein Template ist ;)).

    Doch, FILE_HEADER ist doch ein Template. 🙂

    template<class CharType>struct FILE_HEADER { /* ... */ };
    

    Ups, stimmt. Das hatte ich übersehen.

    CStoll schrieb:

    Auf Anhieb nicht. Solche Anpassungen brauchst du vor allem, wenn du ein bestimmtes Speicher-Layout erzwingen willst. Bei deiner geänderten Struktur könnten z.B. nach den beiden char-Arrays padding-Bytes eingefügt werden, die (a) eventuell unnötigen Speicherplatz verbrauchen und (b) das Layout durcheinanderwürfeln können.

    Schade. 🙂 Falls dir noch mal eines über den Weg läuft, wäre ich dran interessiert. Also sollte es auch noch mit der geänderten Sruktur, wo nicht mehr alle Memeber durch 4 teilbar sind klappen.

    Theoretisch ja, praktisch müsstest du mal sehen, ob dich die Padding-Bytes im Ernstfall stören.

    PS: Laut meiner MSDN ist der Defaultwert für's Padding bei 8, also hast du auch in der Originalversion möglicherweise schon Lücken im Objekt.



  • CStoll schrieb:

    Theoretisch ja, praktisch müsstest du mal sehen, ob dich die Padding-Bytes im Ernstfall stören.

    Also. Ich habe mal Testweise die Struktur mit Zufallswerten gefüllt in einer Schleife in die Datei schreiben lassen und wieder auslesen lassen. Dann mit memcmp die Bytes verglichen und es haut alles hin. Das ist die Struktur

    template<class CharType>
        struct MAIN_HEADER
        {
            CharType             m_pSignature[7];
            short                m_MajorVersion;
            short                m_MinorVersion;
            EncryptionAlgorithm  m_EncryptionAlgorithm;
            CharType             m_pSalt[32];
            int                  m_Counter;
            CharType             m_pReserved[54];
        };
    

    Die "echte" Größe der Struktur ist 105 Bytes. Ohne #pragma pack Angaben - Standardwert 8 - ist die Struktur allerdings 108 Bytes groß. Also wird etwas aufgefüllt. Das mit dem Padding verstehe ich nicht ganz in der MSDN. Wozu dient das?
    Es läuft in meinem Test auch alles mit #pragma pack(1). Hier hat die Struktur dann ihre Originalgröße von 105 Bytes.

    Eine Sache verunsichert mich hier allerdings:

    MSDN schrieb:

    Note that if you change the alignment of a structure, the structure will not use as much space in memory, but you may see a decrease in performance or even get a hardware-generated exception for unaligned access. It is possible to modify this exception behavior with SetErrorMode.

    Ja, habe ich denn jetzt ein Performanzverlust bei #pragma pack(1)?

    CStoll schrieb:

    PS: Laut meiner MSDN ist der Defaultwert für's Padding bei 8, also hast du auch in der Originalversion möglicherweise schon Lücken im Objekt.

    Klingt nett. Ich habe auch eine. 😉



  • OK, dann etwas ausführlicher: Das Padding dient dazu, die Position einzelner Member an "gutartigen" Speicheradressen auszurichten. Nehmen wir also eine etwas einfachere Struktur:

    struct test
    {
      char c;
      int i;
    };
    

    Die Verbindung zwischen Prozessor und Speicher kann heute meist 4 Byte auf einmal übertragen, also ist es günstig, wenn der int-Wert an einer Adresse steht, die durch 4 teilbar ist. da der char nur 1 Byte benötigt, wird das erreicht, indem noch 3 Füllbytes dazwischengeschoben werden (welchen Wert die haben, ist nicht festgelegt*) - damit hat die gesamte Struktur eine Größe von 8 Byte (c(1)+3+4(i)).

    Wenn du das Padding ausschaltest, werden alle Elemente lückenlos hintereinander untergebracht, dadurch reduziert sich die Größe auf 5. Allerdings liegt der int jetzt nicht mehr geeignet im Speicher, um auf einen Schlag ausgelesen zu werden - also muß das Programm für einen Zugriff erst zwei 4-Byte-Blöcke lesen und daraus den int-Wert zusammenbauen (und in der anderen Richtung ihn wieder zerlegen und in Einzelteilen zurückschreiben).

    * das bedeutet auch, daß du mitunter falsche Antworten erhältst, wenn du eine Struktur elementweise kopierst und dann per memcmp() vergleichst:

    test t1 = {'a',4711},t2;
    t2.c=t1.c;t2.i=t1.i;
    cout<<(memcmp(t1,t2)==0)?"gleich":"verschieden";//was hier ausgegeben wird, hängt von den willkürlichen Werten der Padding-Bytes ab
    


  • CStoll schrieb:

    OK, dann etwas ausführlicher: Das Padding dient dazu, die Position einzelner Member an "gutartigen" Speicheradressen auszurichten. Nehmen wir also eine etwas einfachere Struktur:

    struct test
    {
      char c;
      int i;
    };
    

    Die Verbindung zwischen Prozessor und Speicher kann heute meist 4 Byte auf einmal übertragen, also ist es günstig, wenn der int-Wert an einer Adresse steht, die durch 4 teilbar ist. da der char nur 1 Byte benötigt, wird das erreicht, indem noch 3 Füllbytes dazwischengeschoben werden (welchen Wert die haben, ist nicht festgelegt*) - damit hat die gesamte Struktur eine Größe von 8 Byte (c(1)+3+4(i)).

    Wenn du das Padding ausschaltest, werden alle Elemente lückenlos hintereinander untergebracht, dadurch reduziert sich die Größe auf 5. Allerdings liegt der int jetzt nicht mehr geeignet im Speicher, um auf einen Schlag ausgelesen zu werden - also muß das Programm für einen Zugriff erst zwei 4-Byte-Blöcke lesen und daraus den int-Wert zusammenbauen (und in der anderen Richtung ihn wieder zerlegen und in Einzelteilen zurückschreiben).

    Wunderbar. Herzlichen Dank.

    CStoll schrieb:

    * das bedeutet auch, daß du mitunter falsche Antworten erhältst, wenn du eine Struktur elementweise kopierst und dann per memcmp() vergleichst:

    test t1 = {'a',4711},t2;
    t2.c=t1.c;t2.i=t1.i;
    cout<<(memcmp(t1,t2)==0)?"gleich":"verschieden";//was hier ausgegeben wird, hängt von den willkürlichen Werten der Padding-Bytes ab
    

    Das heißt ich könnte mit meinen efrigen memcmp Vergleichen bei allen zig Testdurchläufen immer Glück gehabt haben und sollte lieber jeden Member einzeln vergleichen.

    P.S.: Vor jedem Schreiben eines Header wird dieser intialisiert mit

    memset(&Header, 0x00, sizeof(MAIN_HEADER))
    

    . Das erwischt doch auch die Paddingbytes, sodass memcmp wiederum nicht glücklich verlief sondern "korrekt", oder?



  • Ja, memset() erwischt auch die Padding-Bytes.

    (und bei deinem Ansatz oben (Daten schreiben, wieder einlesen und dann vergleichen) kommt auch aus Prinzip das richtige Ergebnis heraus, weil read() und write() ebenfalls alles ohne Rücksicht auf die interne Struktur lesen bzw. schreiben)

    PS: Problematisch wird es, wenn du zwischen schreiben und lesen das Layout der Struktur geändert hast, d.h. wenn du z.B. einen Header schreibst, dann im Programm die Padding-Einstellungen änderst und mit der neuen Version diesen Header wieder auszulesen versuchst - dabei stimmen die Daten immer noch auf Byte-Ebene überein, aber die einzelnen Bytes gehören nicht mehr zu den selben Membern. Zum Beispiel bei meiner obigen test-Struktur:

    //Lauf 1 mit Standard-Padding:
    test t = {'a',4711};
    file.write((char*)&t,sizeof(test);
    //Ergebnis: 61 xx xx xx 00 00 12 67
    
    //Lauf 2 compiliert mit #pragma pack(1)
    test t;
    file.read((char*)&t,sizeof(test);
    //Ergebnis: 61 xx xx xx 00
    //in t.i kann jetzt alles mögliche drin stehen, aber bestimmt nicht 4711
    


  • CStoll schrieb:

    PS: Problematisch wird es, wenn du zwischen schreiben und lesen das Layout der Struktur geändert hast, d.h. wenn du z.B. einen Header schreibst, dann im Programm die Padding-Einstellungen änderst und mit der neuen Version diesen Header wieder auszulesen versuchst - dabei stimmen die Daten immer noch auf Byte-Ebene überein, aber die einzelnen Bytes gehören nicht mehr zu den selben Membern. Zum Beispiel bei meiner obigen test-Struktur:

    //Lauf 1 mit Standard-Padding:
    test t = {'a',4711};
    file.write((char*)&t,sizeof(test);
    //Ergebnis: 61 xx xx xx 00 00 12 67
    
    //Lauf 2 compiliert mit #pragma pack(1)
    test t;
    file.read((char*)&t,sizeof(test);
    //Ergebnis: 61 xx xx xx 00
    //in t.i kann jetzt alles mögliche drin stehen, aber bestimmt nicht 4711
    

    Das sollte nicht passieren. Werde es einfach ohne eine #pragma pack Anweisung machen, da sie ja hier nicht nötig ist. Vielen Dank auch.


Anmelden zum Antworten