Speicherbereich byteweise verschieben



  • Hallo,

    Aus reiner Neugier habe ich mir eine Frage gestellt. Ich habe schon oft gehört, man dürfe nur PODs byteweise kopieren. Das leuchtet mir auch ein, da im objektorientierten Umfeld oft zusätzliche Abhängigkeiten (Zeiger/Referenzen), VTables und Vererbung im Spiel sind, die bei byteweiser Duplizierung zu Problemen führen können.

    Wie sieht es mit dem Verschieben aus? Ich meine damit den Fall, bei dem ein roher Speicherbereich mit dem Inhalt eines Objekt byteweise überschrieben wird, worauf man das alte Objekt nicht mehr verwendet und dessen Speicher freigibt.

    typedef IrgendeinKlassentyp type;
    const size_t size = sizeof(type);
    
    // altes Objekt auf übliche Weise konstruieren
    type* src = new type();
    
    // rohen Speicher für neues Objekt anfordern
    type* dst = static_cast<type*>(operator new(size));
    
    // Speicher byteweise kopieren
    memcpy(dst, src, size);
    
    // alten Speicher freigeben, ohne Destruktor aufzurufen
    operator delete(src);
    
    // neues Objekt ordnungsgemäss zerstören
    delete dst;
    

    Eigentlich sollte das ja gehen, weil die Klasse 1:1 abgebildet wird, also inklusive VTable und allem. Auch das Alignment sollte okay sein. Ich sehe nur ein Problem, wenn Referenzen oder Zeiger auf das alte Objekt verweisen.

    Deshalb meine Frage: Führt mein Vorgehen zu undefiniertem Verhalten?



  • Interessante Frage, hab jetzt auf die Stelle nichts direkt im Standard gefunden was dagegen spricht. Könnte mir aber vorstellen dass das Probleme gibt wenn Pointer relativ sind (dann wäre selbst der vptr nach der Kopie im Eimer).



  • nö verschieben is genausowenig erlaubt.
    was dagegen spricht? ich mag die stelle im standard jetzt nicht suchen. aber im prinzip isses...
    wenn man an stelle X kein objekt mit typ T konstruiert hat, dann darf man auf stelle X auch nicht als T zugreifen. und ein T mit memcpy mit einem anderen T überschreiben, bzw. inhalte austauschen, ist auch verboten. wobei T eben ein nicht-POD UDT ist.

    der grund dass es nicht erlaubt ist, ist vermutlich einfach um die implementierung nicht unnötig einzuschränken.

    es gibt ja z.b. solche sachen wie relative addressierung, und eine implementierung könnte beschliessen z.b. den vtable-zeiger relativ zur adresse des objekts zu codieren (warum auch immer). oder diversen stub-code in jedes objekt reinschreiben - der dann gerne mal relativ adressierte sprünge enthalten kann.

    oder ganz was anderes: eine implementierung dürfte ohne weiters intern irgendwelche globalen tabellen verwenden, wo diverse daten für RTTI features mitgeschrieben werden. also halt adresse und typ in irgendwelchen tabellen eintragen. auch virtuelle funktionen liessen sich theoretisch so umsetzen, ganz ohne vtable-zeiger.

    das ist alles etwas an den haaren herbeigezogen, aber der standard erlaubt eben solche implementierungen.



  • Kann man nicht einfach placement new mit Copykonstruktor verwenden?



  • Nexus schrieb:

    Wie sieht es mit dem Verschieben aus? Ich meine damit den Fall, bei dem ein roher Speicherbereich mit dem Inhalt eines Objekt byteweise überschrieben wird, worauf man das alte Objekt nicht mehr verwendet und dessen Speicher freigibt.

    nimm dies, du bursche!

    class Window
    {
    public:
       Window(){
          theWindowManager.add(this);
       }
       ~Window(){
          theWindowManager.remove(this);
       }
    ...
    };
    


  • Ich sehe das auch so, wie hustbaer. Du verlässt dich auf Sachen, die nicht unbedingt sein müssen. (natürlich sehr wahrscheinlich so gelöst sind, aber verlassen kann man sich darauf laut Standard nicht). Die Implementierung wird da vielleicht noch Metadaten für die Positionen mitschreiben (ich denke mal Debug Sachen) und so würdest du das irgendwie durcheinander bringen.



  • pumuckl schrieb:

    Könnte mir aber vorstellen dass das Probleme gibt wenn Pointer relativ sind (dann wäre selbst der vptr nach der Kopie im Eimer).

    Wie meinst du das genau mit relativen Zeigern?

    hustbaer schrieb:

    der grund dass es nicht erlaubt ist, ist vermutlich einfach um die implementierung nicht unnötig einzuschränken.

    Okay, das klingt logisch. Ist ja bei sehr vielen Dingen in C++ so, dass es eigentlich immer geht, aber genauso gut nicht gehen darf. Wie letztes Mal das Lesen (nicht Dereferenzieren) von nicht initialisierten Zeigern. Gut, man ermöglicht dann mehr für die Implementierung. Aber eigentlich finde ich es schade, dass man sich auf gewisse Low-Level-Dinge kaum verlassen kann (auch wenn man diese in der Praxis kaum braucht).

    knivil schrieb:

    Kann man nicht einfach placement new mit Copykonstruktor verwenden?

    Doch, natürlich. Es ging mir hier ja nur darum, ob es prinzipiell erlaubt wäre, byteweise zu kopieren. Auch wenn das nicht das Anliegen dieses Threades war, könnte ich mir vorstellen, dass man dadurch gewisse Optimierungen hätte erzielen können. Zum Beispiel für ein swap() , dessen Argumente nicht ihrerseits auf swap() optimiert sind. Aber wie gesagt, das nur so am Rande...

    volkard schrieb:

    nimm dies, du bursche!

    Hehe. Wenn man natürlich Referenzen oder Zeiger auf dieses Objekt speichert, ist klar, dass es zu Problemen führen kann. 😉



  • Nexus schrieb:

    volkard schrieb:

    nimm dies, du bursche!

    Hehe. Wenn man natürlich Referenzen oder Zeiger auf dieses Objekt speichert, ist klar, dass es zu Problemen führen kann. 😉

    deswegen geht nicht

    typedef IrgendeinKlassentyp type;
    

    mit dem ziel, das in eine template-containerklasse zu stopfen.
    aber ansonsten gehts auf alles compilern, die ich kenne.
    aber gibts nicht in absehbarer zukunft rvalue-referenzen, die genau das schnelle verschieben dann erlauben sollen? doch, da war sowas in planung.



  • Okay, das stimmt natürlich.

    RValue-Referenzen sollten doch eher Kopien von Temporaries verhindern? Ich weiss nicht gerade, wie man diese zum Beispiel ein Swap effizient machen sollten (klar, das Problem mit Swap hat man nicht, wenn die verwendeten Klassen durchdacht sind und selber eine effiziente Tauschfunktion anbieten).



  • Nexus schrieb:

    Okay, das stimmt natürlich.

    RValue-Referenzen sollten doch eher Kopien von Temporaries verhindern?

    Nö. Eher beschleunigen.



  • hustbaer schrieb:

    Nexus schrieb:

    Okay, das stimmt natürlich.

    RValue-Referenzen sollten doch eher Kopien von Temporaries verhindern?

    Nö. Eher beschleunigen.

    aber auch das kopieren vermeiden.
    mit rvalue-reverenzen werde ich unkopierbare sachen wie ofstreams mit push_back in eine liste stopfen können und vrauche keine ptr-container mehr.



  • Nexus schrieb:

    knivil schrieb:

    Kann man nicht einfach placement new mit Copykonstruktor verwenden?

    Doch, natürlich. Es ging mir hier ja nur darum, ob es prinzipiell erlaubt wäre, byteweise zu kopieren. Auch wenn das nicht das Anliegen dieses Threades war, könnte ich mir vorstellen, dass man dadurch gewisse Optimierungen hätte erzielen können. Zum Beispiel für ein swap() , dessen Argumente nicht ihrerseits auf swap() optimiert sind. Aber wie gesagt, das nur so am Rande...

    Für ein schnelles swap() kannst du einmal in Notes on Programming blätern, dort entwirft Stepanov eine Implementierung die ziemlich effizient ist.



  • hustbaer schrieb:

    Nö. Eher beschleunigen.

    Aber die Beschleunigung erfolgt ja dadurch, dass weniger kopiert werden muss, oder etwa nicht?

    volkard schrieb:

    mit rvalue-reverenzen werde ich unkopierbare sachen wie ofstreams mit push_back in eine liste stopfen können und vrauche keine ptr-container mehr.

    Hmm... Gut. 🙂
    Aber war das auch eine Absicht hinter RValue-Referenzen oder mehr ein netter Nebeneffekt?

    Stepanov rockt schrieb:

    Für ein schnelles swap() kannst du einmal in Notes on Programming blätern, dort entwirft Stepanov eine Implementierung die ziemlich effizient ist.

    Vielen Dank, das scheint interessant zu sein, ich bins gerade am Lesen... 😉


Anmelden zum Antworten