Was kommt zuerst: Destruktor oder Deallokation ?



  • Hallö!

    Ich habe eigentlich nur eine kleine Frage:
    Ist der Destruktor nur eine normale Funktion, oder vollführt der noch irgendwelchen Zauber hinter den Kulissen?

    Also wird ein Objekt dealloziiert und dabei einfach der Dtor aufgerufen, oder wird der Dtor aufgerufen, der dann das Objekt zerstört??
    Noch kürzer:
    Deallokator -> Destruktor, oder
    Destruktor -> Deallokator

    Ich habe mal testweise den Dtor mehrfach aufgerufen, ohne ersichtliche Folgen (Objekt schien noch zu existieren), aber wer weiß, ob es sich nicht um das gefürchtete undefinierte Verhalten handelt..?!

    MfG
    Spaghettimann



  • Was wäre denn total immer sinnvoll?



  • Prinzipiell ist der DTor auch "nur" eine normale Methode, da passiert keine Magic im hintergrund.

    Spaghettimann schrieb:

    Deallokator -> Destruktor, oder
    Destruktor -> Deallokator

    Das erste ergibt schonmal garkeinen sinn, wenn der Speicher freigegeben wurde könnte der DTor nichtmehr auf seine eigenen Klassenvariablen zugreifen. Das Zeite ist der fall.



  • Spaghettimann schrieb:

    Ich habe mal testweise den Dtor mehrfach aufgerufen, ohne ersichtliche Folgen (Objekt schien noch zu existieren), aber wer weiß, ob es sich nicht um das gefürchtete undefinierte Verhalten handelt..?!

    Ists, bin zwar gerade zu faul das Zitat rauszusuchen, aber ists.
    Nach dem Destruktoraufruf endet die lifetime des objects, es ist danach zerstört. Bevor ein anderes erstellt werden kann, muss sie erst wieder via placement new beginnen; das hier wäre also legal:

    test t;
    ...
    t.~t(); // zerstöre t
    ::new(static_cast<void*>(&t)) test(); // beginne ein neues Objekt an der Adresse
    ...
    // t wird hier vom compiler zerstört
    

    (sollte man aber trotzdem nicht machen)

    Zur Deallozierung: Container, die einen Allocator verwenden, nutzen folgende zwei Zeilen:

    alloc.destroy(ptr); // ruft Destruktor auf
    alloc.deallocate(ptr, 1); // gibt Speicher frei
    

    (vereinfacht und nur ein Element)
    Andersherum macht das ja auch wie gesagt keinen Sinn.



  • Nathan schrieb:

    Ists, bin zwar gerade zu faul das Zitat rauszusuchen, aber ists.

    No, it isn't.

    Nach dem Destruktoraufruf endet die lifetime des objects, es ist danach zerstört.

    Nur, wenn der Destruktor nicht-trivial ist ([basic.life]/1).



  • argumentclinic schrieb:

    Nathan schrieb:

    Ists, bin zwar gerade zu faul das Zitat rauszusuchen, aber ists.

    No, it isn't.

    Yes, it is:

    [class.dtor]/15 schrieb:

    Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended.


  • Mod

    Nathan schrieb:

    argumentclinic schrieb:

    Nathan schrieb:

    Ists, bin zwar gerade zu faul das Zitat rauszusuchen, aber ists.

    No, it isn't.

    Yes, it is:

    [class.dtor]/15 schrieb:

    Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended.

    Dieses Zitat ist defekt.

    PS:

    new (&t) test;
    

    tuts auch, nicht?



  • tkausl schrieb:

    Prinzipiell ist der DTor auch "nur" eine normale Methode, da passiert keine Magic im hintergrund.

    Ja. Abgesehen vom automatischen "Destructor-Chaining". 🤡

    @Arcoth
    Wie lautet denn das nicht-kaputte Zitat?
    BTW: Hast du zufällig nen Link zum aktuellen Working-Draft parat?


  • Mod

    hustbaer schrieb:

    @Arcoth
    Wie lautet denn das nicht-kaputte Zitat?

    Ein trivialer Destruktor sollte ein Objekt (konzeptionell) nicht zerstören. Es ist ein no-op.

    Zudem ist der Begriff der "Existenz" eben für ein Objekt hier nicht angebracht. Siehe http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1285

    Man kann demnach folgendes als "das nicht-kaputte Zitat" betrachten:

    §12.7/1 schrieb:

    For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.

    Wobei angemerkt werden sollte, dass der Standard zum Thema lifetime recht schwammig ist. Das muss man bis 1Z aufräumen.

    Edit:

    BTW: Hast du zufällig nen Link zum aktuellen Working-Draft parat?

    http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4527.pdf



  • Arcoth schrieb:

    Nathan schrieb:

    argumentclinic schrieb:

    Nathan schrieb:

    Ists, bin zwar gerade zu faul das Zitat rauszusuchen, aber ists.

    No, it isn't.

    Yes, it is:

    [class.dtor]/15 schrieb:

    Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended.

    Dieses Zitat ist defekt.

    Bitte, was?

    PS:

    new (&t) test;
    

    tuts auch, nicht?

    Nope.

    #include <iostream>
    #include <new>
    
    struct test {};
    
    void* operator new(std::size_t, test *ptr)
    {
    	std::cout << "hi\n";
    	return ptr;
    }
    
    int main()
    {
    	test t;
    	new(&t) test();
    }
    

    (Zugegeben, selbes Level Benutzung std::addressof, aber wenn man oft placement news in generischem Code braucht, hats man irgendwie drin)



  • Arcoth schrieb:

    hustbaer schrieb:

    @Arcoth
    Wie lautet denn das nicht-kaputte Zitat?

    Ein trivialer Destruktor sollte ein Objekt (konzeptionell) nicht zerstören. Es ist ein no-op.

    Wenn man jetzt nur nicht-triviale Destruktoren betrachtet, dann ist da kein Widerspruch und folgich ist es UB. Bei trivialen Destruktoren gibts den Widerspruch.

    Edit: Sorry, Doppelpost.



  • Arcoth schrieb:

    hustbaer schrieb:

    @Arcoth
    Wie lautet denn das nicht-kaputte Zitat?

    Ein trivialer Destruktor sollte ein Objekt (konzeptionell) nicht zerstören. Es ist ein no-op.

    Das könnte man so definieren, ja. Ich verstehe bloss den Nutzen nicht. Welchen (realen) Vorteil hätte man dadurch?



  • Arcoth schrieb:

    Dieses Zitat ist defekt.

    Kauf dir ein Argument. Du hast das System durchbrochen, jetzt müsste der Thread eigentlich von der Forenpolizei geschlossen werden.

    hustbaer schrieb:

    tkausl schrieb:

    Prinzipiell ist der DTor auch "nur" eine normale Methode, da passiert keine Magic im hintergrund.

    Ja.

    Nein.

    hustbaer schrieb:

    Arcoth schrieb:

    hustbaer schrieb:

    @Arcoth
    Wie lautet denn das nicht-kaputte Zitat?

    Ein trivialer Destruktor sollte ein Objekt (konzeptionell) nicht zerstören. Es ist ein no-op.

    Das könnte man so definieren, ja. Ich verstehe bloss den Nutzen nicht. Welchen (realen) Vorteil hätte man dadurch?

    Z.B: Ein unsigned char-Objekt ist immer gültig?



  • argumentclinic schrieb:

    hustbaer schrieb:

    tkausl schrieb:

    Prinzipiell ist der DTor auch "nur" eine normale Methode, da passiert keine Magic im hintergrund.

    Ja.

    Nein.

    Weil?

    argumentclinic schrieb:

    hustbaer schrieb:

    Arcoth schrieb:

    hustbaer schrieb:

    @Arcoth
    Wie lautet denn das nicht-kaputte Zitat?

    Ein trivialer Destruktor sollte ein Objekt (konzeptionell) nicht zerstören. Es ist ein no-op.

    Das könnte man so definieren, ja. Ich verstehe bloss den Nutzen nicht. Welchen (realen) Vorteil hätte man dadurch?

    Z.B: Ein unsigned char-Objekt ist immer gültig?

    Für char, signed char und unsigned char gibt es sowieso eigene Regeln. Du darfst eine Speicherstelle jederzeit als (signed/unsigned) char ansprechen, auch wenn da nie ein (signed/unsigned) char reinkonstruiert wurde.
    Dass ein unsigned char-Objekt ist immer gültig ist ändert sich also nicht, egal ob man definiert dass ein trivialer Destruktor ein Objekt zerstört oder nicht.



  • ps:

    Arcoth schrieb:

    Edit:

    BTW: Hast du zufällig nen Link zum aktuellen Working-Draft parat?

    http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4527.pdf

    Danke 🙂


  • Mod

    hustbaer schrieb:

    Das könnte man so definieren, ja. Ich verstehe bloss den Nutzen nicht. Welchen (realen) Vorteil hätte man dadurch?

    Ich kann keinen erkennen. Jedenfalls ist die Ideologie des Standards die, das e.g. PODs genau wie fundamentale Typen nicht durch einen (Pseudo-)Destruktoraufruf zerstört werden können. Sie sind einfach ein paar zusammenhängende Bytes, die vom abstrakten Modell des Konstruktors/Destruktors verschont sind.

    Wie auch immer, die Definition von object lifetime ist - wie bereits erwähnt - gerade völlig schrott und vage, deswegen habe ich auch nicht wirklich Lust darüber zu diskutieren solange kein entsprechendes Paper vorliegt, das eine sinnvolle resolution parat hat.

    Nope.

    Mir ging es darum, dass du eine Zeile in einem simplen Pseudocode gezeigt hast, die sonst nur in e.g. std::allocator<>::construct vorhanden ist. 🙂 §3.8 castet this auch nicht gleich nach void* . Musst doch nicht immer gleich deine volle Generizitätskanone raushauen.

    @hustbaer: Da gibt's nix zu danken. Einfach bei isocpp.org nach links gucken. Oder im repo von cppdraft schauen.


  • Mod

    Du darfst eine Speicherstelle jederzeit als (signed/unsigned) char ansprechen,

    unsigned char und char , nicht aber signed char .



  • Arcoth schrieb:

    Du darfst eine Speicherstelle jederzeit als (signed/unsigned) char ansprechen,

    unsigned char und char , nicht aber signed char .

    Wurde das geändert? Bin mir ziemlich sicher dass die Spezialregel in C++03 für alle 3 galt.

    EDIT: OK, hab grad selbst nachgesehen... waren immer nur char und unsigned char . Krass. Muss ich mit der "was gilt als String" Regel bei ostream << T* verwechselt haben.



  • Ich war ja unerwartet viel los. Hätte nicht gedacht, dass ich mit der knappen Frage soviel Wind erzeuge...
    Hat zwar bislang keine Probleme bereitet, aber jetzt bin ich doch etwas verunsichert. Also doch sicherheitshalber den Dtor in extra Funktion auslagern und separat aufrufen...

    Ich hatte zwar zwischendurch eine Idee, warum es (zuminest in meinem Fall) gehen sollte. Die ist mir aber schon wieder abhanden gekommen...

    Eine Frage vielleicht noch:
    Speicher reservieren/freigeben hat doch keine Auswirkungen auf den momentanen Inhalt der Speicherstelle, oder??
    Wenn ich also direkt nach der Freigabe darauf zugreife, sollte doch der alte Inhalt noch nicht überschrieben sein?!
    Wer genau kümmert sich eigentlich um die Speicherverwaltung: Der Compiler oder das OS?!
    Wenns der Compiler wäre, würde ich nicht verstehen, warum es als kostspielig angesehen wird.
    Aber wenns das OS ist, muss ja für jeden Speicherbereich den ich mir reserviere wieder im Speicher darüber buchgeführt werden... Also "jedes int frisst 2*sizeof(int) Speicher"??

    Sind wohl Grundlagen rund um den Computer.. Aber die fehlen halt, wenn man Programmieren nur über diverse Tutorials ausm Netz lernt..


  • Mod

    Speicher reservieren/freigeben hat doch keine Auswirkungen auf den momentanen Inhalt der Speicherstelle, oder??

    Das ist irrelevant. Sie existiert danach für dich nicht mehr.

    Also "jedes int frisst 2*sizeof(int) Speicher"??

    Ja, vielleicht; aber du wirst ja nicht einen einzelnen int allozieren. Und 8 byte sind recht wenig im Vergleich zu e.g. einer Million.



  • Spaghettimann schrieb:

    Wer genau kümmert sich eigentlich um die Speicherverwaltung: Der Compiler oder das OS?!

    Kommt auf die Implementierung an. Wieso sollte das aber nen Unterschied machen? Auch das OS kann Dinge im Usermode machen. Ob der Code in einer Library steht die vom OS oder vom Compiler bereitgestellt wird, macht letztlich keinen Unterschied.

    Wobei: primär ist natürlich der Compiler bzw. "die Implementierung" zuständig. Nur gibt es eben Implementierungen die in malloc bzw. ::operator new einfach nur sofort an eine OS Funktion weitergeben.

    Spaghettimann schrieb:

    Wenns der Compiler wäre, würde ich nicht verstehen, warum es als kostspielig angesehen wird.
    Aber wenns das OS ist, muss ja für jeden Speicherbereich den ich mir reserviere wieder im Speicher darüber buchgeführt werden... Also "jedes int frisst 2*sizeof(int) Speicher"??

    Njjjjjjaaa-nein 🙂

    Also gut, ja. In gewissen Fällen könnte der Compiler sagen "hier wird genau ein T zerstört, T ist 123 Byte gross, also geb' ich jetzt mal 123 Byte frei".

    Aber: es gibt auch Heap-Implementierungen die siche die Grösse zu jeder Allokation selbst "merken", die viele Allokationen (speziell solche mit Grösse = 1, 2, 4, 8 Byte und etliche andere übliche Grössen) ganz ohne Overhead machen können. Weil sie anhand der Adresse des Speichers auf die Grösse des Blocks rückschliessen können.
    Ich glaube sogar dass die meisten mainstream OS' mittlerweile mit solchen Heap-Implementierungen ausgeliefert werden.


Anmelden zum Antworten