Speicherverwaltung



  • Moin,

    ich hab ein Problem mit der Speicherverwaltung von C++.

    Ich habe eine ganz normale Klasse:

    class GanzNormaleKlasse{
     GanzNormaleKlasse();
     ~GanzNormaleKlasse();
    };
    

    Jetzt gibt es ja zwei Möglichkeiten für diese Klasse Speicher zu reservieren:

    1.) GanzNormaleKlasse gnk;
    2.) GanzNormaleKlasse* pGnk = new GanzNormaleKlasse();
    

    Bei der ersten Möglichkeit kümmert C++ sich darum, dass der Speicher wieder freigegeben wird.
    Und genau das möchte ich umgehen.
    Gibt es eine Möglichkeit zu verbieten oder zu erkennen, dass ein Objekt dieser Klasse mit der ersten Variante angelegt wurde?

    Am liebsten möchte ich es zum Kompilezeitpunkt verhindern. Aber zur Laufzeit reicht auch, wenn dann z.B. eine Exception geworfen wird.

    ---------

    Dann hab ich noch eine zweite Frage:
    Wie kann ich erkennen, ob ein Objekt auf dem Stack oder auf dem Heap liegt?

    Habe mir bis jetzt so geholfen, dass ich im Konstruktor eine Variable anlege, die dann ja sicher aufm Stack liegt, und habe dann die Zeiger von this und der Variable verglichen. Ist das ggf. von System zu System anders? Kompilerabhängig?

    GanzNormaleKlasse::GanzNormaleKlasse()
    {
      char stack;
      if( this > (void*)&stack ){
        std::cout << " *** Object on Stack is not allowed" << std::cout;
        throw "Object on Stack!";
      }
    }
    


  • if( this > (void*)&stack )
    

    Bin mir nicht sicher, dass das geht. Ich glaube aber eher nicht.

    Ich hab vor Jahren mal über ein ähnliches Problem gelesen. Da ging es um die Frage, ob aufräumen mit delete oder nicht (delete auf Stack-Object ist böse). Dort wurde der new-Operator so überladen, dass er eine Variable im Objekt setzt, wenn das Objekt dynamisch erstellt wurde. Ich erinnere mich allerdings nur unscharf daran.



  • Mach die Konstruktoren private, und biete statische Klassenfunktionen an, mit welchen Du die privaten Konstruktoren aufrufst:

    class C
    {
    public:
       static std::shared_ptr<C> create(args)
       {
          return std::shared_ptr<C>(new C(args));
       }
    private:
       C(args){...}
    };
    

    Siehe hierzu auch nach dem Named-Constructor Idiom.



  • matti83 schrieb:

    Bei der ersten Möglichkeit kümmert C++ sich darum, dass der Speicher wieder freigegeben wird.
    Und genau das möchte ich umgehen.
    Gibt es eine Möglichkeit zu verbieten oder zu erkennen, dass ein Objekt dieser Klasse mit der ersten Variante angelegt wurde?

    Wie wäre es, wenn du (wie oben erwähnt) die Konstuktoren einfach private setzt und dann CreateXXX -Funktionen erstellst?

    class MyClass
    {
    public:
        //Statisch. Wenn man nur über ein Objekt auf diese Funktion zugreifen
        //könnte, die Funktion aber als einzige Methode ein Objekt zurückliefert,
        //wie soll man dann auf die Funktion zugreifen? ;)
        static MyClass*CreateObject();
        //Nicht statisch. Ansonsten müsste man den this-Zeiger explizit angeben, und
        //das wäre hirnverbrannt.
        void DropObject();
    private:
        //Die jetzt privaten Elemente.
        MyClass(){}
        ~MyClass(){}
    };
    
    MyClass*MyClass::CreateObject()
    {
        return new MyClass();
    }
    
    void MyClass::DropObject()
    {
        this->~MyClass();
    }
    


  • void MyClass::DropObject()
    {
        this->~MyClass(); // Pfui, dtor aufrufen macht man wenn überhaupt nur bei Objekten die mit Placement-new erstellt wurden, weil es dafür kein delete-Äquivalent gibt.
        // besser
        delete this;  //  :warning: als allerletzte Anweisung, sonst reißt du dir beim Aufräumen den Stuhl unterm Hintern weg
    }
    

    Die Variante ist auch sinnvoller, weil nicht mehr ersichtlich ist, wo den das Objekt liegt. Und demzufolge, dass es mit new angelegt wurde.



  • Absolut hässlich, nicht threadsafe und daher nicht zu empfehlen:

    #include <iostream>
    
    class nostack {
      nostack(const nostack& other);
      static nostack *to_construct;
    public:
      nostack() {
        if (this != to_construct) throw std::bad_alloc();
        else to_construct = 0;
      }
      void *operator new(std::size_t num_bytes) {
        return reinterpret_cast<void *>
          (to_construct = reinterpret_cast<nostack *>(new char[num_bytes]));
      }
      void operator delete(void *memory, std::size_t) {
        return delete[] reinterpret_cast<char *>(memory);
      }
    };
    nostack * nostack::to_construct;
    
    int main()
    {
      std::cout << "stack: ";
      try { { nostack a; } std::cout << "geglückt\n"; }
      catch(std::bad_alloc) { std::cout << "missglückt\n"; }
    
      std::cout << "heap : ";
      try { { nostack *b = new nostack; delete b; } std::cout << "geglückt\n"; }
      catch(std::bad_alloc) { std::cout << "missglückt\n"; }
    }
    
    stack: missglückt
    heap : geglückt
    

    Das mit dem create_xx ist da schon viel besser.

    Grundsätzlich sehe ich aber keinen Grund, wieso man verbieten sollte, etwas auf dem Stack anzulegen.



  • Vielen Dank für die schnelle Hilfe.
    Das mit dem Named-Constructor Idiom ist eine coole Sache - wobei der Destructor ja durchaus Public sein dürfte.



  • matti83 schrieb:

    [...]wobei der Destructor ja durchaus Public sein dürfte.

    Genau Dann reicht auch ein einfaches delete auf den Zeiger. Was der ganze Quatsch mit den privaten Dtors und die daraus resultierenden Gruselkonstrukte sollen, erschließt sich mir absolut nicht.



  • plaissment schrieb:

    Absolut hässlich, nicht threadsafe und daher nicht zu empfehlen:

    #include <iostream>
    
    class nostack {
      nostack(const nostack& other);
      static nostack *to_construct;
    public:
      nostack() {
        if (this != to_construct) throw std::bad_alloc();
        else to_construct = 0;
      }
      void *operator new(std::size_t num_bytes) {
        return reinterpret_cast<void *>
          (to_construct = reinterpret_cast<nostack *>(new char[num_bytes]));
      }
      void operator delete(void *memory, std::size_t) {
        return delete[] reinterpret_cast<char *>(memory);
      }
    };
    nostack * nostack::to_construct;
    
    int main()
    {
      std::cout << "stack: ";
      try { { nostack a; } std::cout << "geglückt\n"; }
      catch(std::bad_alloc) { std::cout << "missglückt\n"; }
    
      std::cout << "heap : ";
      try { { nostack *b = new nostack; delete b; } std::cout << "geglückt\n"; }
      catch(std::bad_alloc) { std::cout << "missglückt\n"; }
    }
    
    stack: missglückt
    heap : geglückt
    

    Das mit dem create_xx ist da schon viel besser.

    Grundsätzlich sehe ich aber keinen Grund, wieso man verbieten sollte, etwas auf dem Stack anzulegen.

    Kann ich so denn wirklich ausschliessen, dass es sich auf dem stack oder dem heap befindet? oder kann ich so nur sagen, welcher Constructor benutzt wurde.

    Denn was passiert denn, wenn ich diese Klasse als Memberobjekt einer anderen Klasse verwende und diese mit new erstelle?

    class Damn{
     public:
      nostack ns;
    };
    
    Damn* d = new Damn();
    


  • matti83 schrieb:

    Gibt es eine Möglichkeit zu verbieten oder zu erkennen, dass ein Objekt dieser Klasse mit der ersten Variante angelegt wurde?

    Nein. Und die Codes in diesem Thread sind (abgesehen von Tachyons) grauenhaft und führen zu undefiniertem Verhalten.

    Warum es nicht sinnvoll ist, dass eine Klasse versucht, Freestore/Stack zu unterscheiden, erfahrt ihr hier. Die wesentliche Punkte sind: Das Vorgehen ist alles andere als benutzerfreundlich, sondern wehrt sich gegen die Intuition des Anwenders und gängige C++-Praxis. Es nimmt dem Anwender die Möglichkeit, den Speicherverwaltungsmechanismus zu bestimmen und führt zu unerwarteten Fehlern.

    Lest unbedingt Shade Of Mines Beiträge im verlinkten Thread, er bringt es sehr gut auf den Punkt.



  • Hmm.. hab es mir durchgelesen. Fazit: es geht nicht!? 😕



  • Nein. Fazit: Es macht keinen Sinn, das zu versuchen.

    Es geht zwar auch nicht, das stimmt, aber selbst wenn es ginge, wäre das Vorgehen nicht zu empfehlen.



  • Will ich irgendwie nicht verstehen! Was Sinn macht und was nicht bleibt doch dem Künstler überlassen.



  • matti83 schrieb:

    Will ich irgendwie nicht verstehen! Was Sinn macht und was nicht bleibt doch dem Künstler überlassen.

    Nein, du kannst nicht nur an dich gerade jetzt denken. Zumindest falls du später mal vorhast, dass andere Leute deinen Code lesen oder sogar benutzen sollen (in Form einer Bibliothek oder einem gemeinsamen Projekt). Oder auch falls du selbst in zwei Jahren nochmals diesen Code anschaust und dann über deine eigene "Benutzerfreundlichkeit" stolperst.

    Und "Künstler"... Naja, Programmieren beinhaltet zwar viele kreative Aspekte, aber auch sehr viele festgelegte Regeln. Und du musst dich an die Konzepte der Sprache halten, in der du programmierst. In C++ wäre so ein Konzept: Programmiere nicht wie in Java. D.h. bevormunde den Benutzer nicht. Wenn der Benutzer seine Objekte dynamisch auf dem Heap anlegt, masse dir nicht an, diese zu löschen (es sei denn, du hast eine konsequente eindeutige Semantik und kein intransparentes Gemisch mit Stack-Objekten).

    Aber hast du den anderen Thread wirklich gelesen? Ich begreife nämlich nicht ganz, wie man nach all den Argumenten immer noch eine Heap/Stack-Unterscheidung durchführen will. Denn grundsätzlich bist du sicher daran interessiert, mehr oder weniger "sauberen" Code zu haben. Das bezieht sich auf Punkte wie Wartbarkeit, Übersicht, Aussagekraft, Fehleranfälligkeit, Einfachheit etc. Es hat nur Vorteile, wenn du deinen Code intuitiv schreibst. Das kannst du sicher nachvollziehen, oder?



  • ich bin dabei mir ein GC zu schreiben. Jetzt habe ich einer Pointerklasse, die auf meine Objekte zeigt. Diese Pointerklasse kann jetzt auf dem Stack liegen oder im Heap. Aber nur wenn sie im Stack ist interessiert sie mich. Die andere will ich ignorieren. Im heap ist sie, wenn sie z.B. selbst member eines Objektes ist.



  • Tachyon schrieb:

    Mach die Konstruktoren private, und biete statische Klassenfunktionen an, mit welchen Du die privaten Konstruktoren aufrufst:

    class C
    {
    public:
       static std::shared_ptr<C> create(args)
       {
          return std::shared_ptr<C>(new C(args));
       }
    private:
       C(args){...}
    };
    

    Siehe hierzu auch nach dem Named-Constructor Idiom.

    Immer den teuersten aller Pointer empfehlen...
    shared_ptr wird viel zu häufig missbraucht, weil sich Entwickler zu wenig
    Gedanken über Objektverantwortungen machen...



  • matti83^^ schrieb:

    ich bin dabei mir ein GC zu schreiben. Jetzt habe ich einer Pointerklasse, die auf meine Objekte zeigt. Diese Pointerklasse kann jetzt auf dem Stack liegen oder im Heap. Aber nur wenn sie im Stack ist interessiert sie mich. Die andere will ich ignorieren. Im heap ist sie, wenn sie z.B. selbst member eines Objektes ist.

    Falls du der Gleiche bist wie matti83: Du hast rein gar nicht verstanden, was ich und die Leute im verlinkten Thread sagen wollten. Lies dir den Thread richtig durch und überleg dir in Ruhe, warum dein Vorhaben Unsinn ist.

    Yeah... I like it... schrieb:

    Immer den teuersten aller Pointer empfehlen...
    shared_ptr wird viel zu häufig missbraucht, weil sich Entwickler zu wenig
    Gedanken über Objektverantwortungen machen...

    Gut, dass ich nicht der einzige bin, der das so sieht. 👍



  • Hallo Nexus.

    Anscheinend hab ich es wirklich nicht verstanden. Ich wäre dir sehr verbunden, wenn du mir kurz und knapp den Kern der Aussage hier einmal versuchst wieder zu spiegeln.

    Denn unter Umständen hab ich es auch nicht geschafft dir mein Problem vernünftig zu schildern.

    Freundliche Grüße



  • Vielleicht haben wir auch aneinander vorbeigeredet. Nochmals die wichtigsten Gründe, warum es nicht sinnvoll ist, wenn eine Klasse sich unterschiedlich verhält, wenn ihre Objekte automatisch oder dynamisch angefordert wurden. Ganz unabhängig davon, ob die Unterscheidung überhaupt realisierbar ist.

    • Du versuchst mit dem Ansatz, benutzerfreundlich zu sein, und den Speicher automatisch freizugeben, wenn er vom Benutzer dynamisch angefordert wurde. Die Idee ist prinzipiell sehr gut, aber das mit der Benutzerfreundlichkeit geht komplett nach hinten los.
    • Der Grund ist, dass du Konzepte aus anderen Sprachen (Garbage Collector) 1:1 nach C++ übertragen willst, wo Speicherverwaltung aber ganz anders abläuft. C++ hat viel strengere und eindeutigere Besitzverhältnisse als Sprachen mit GC (Speicher ist nicht geteilt, sondern hat meist einen klaren Besitzer). Normalerweise wird der Speicher von dem freigegeben, der ihn anfordert. Mit deinem Versuch verstösst du gegen diese Konvention.
    • Dadurch rufst du beim Benutzer Verwirrung hervor. Wenn der Benutzer ein einfaches new verwendet, geht er davon aus, den Speicher ohne Probleme mit delete freigeben zu können. Er wird sich wundern, wenn ganz normaler Code einfach abstürzt.
    • Gleichzeitig nimmst du dem Anwender mit dem GC die Freiheit, sich Speicher irgendwie zu verschaffen. Er kann den Stack benutzen, den Heap ( malloc() aus C), den Freestore ( new ) oder irgendeinen anderen Allokator. Mit einem automatischen delete in deiner Bibliothek zwingst du ihn zur Verwendung des new -Operators.
    • In C++ benutzt man das RAII-Idiom, um manuelle Speicherverwaltung zu vermeiden. Damit muss gar niemand mehr explizit den Speicher freigeben, sondern die Aufgabe wird an eine Klasse (z.B. Smart-Pointer) delegiert. Damit ist der Grund dafür, überhaupt einen automatischen Freigabemechanismus in deiner Bibliothek einzurichten, hinfällig.


  • * Du versuchst mit dem Ansatz, benutzerfreundlich zu sein, und den Speicher automatisch freizugeben, wenn er vom Benutzer dynamisch angefordert wurde. Die Idee ist prinzipiell sehr gut, aber das mit der Benutzerfreundlichkeit geht komplett nach hinten los.

    Den Punkt kann ich abhaken, weil der Benutzer genau weiss was er macht, wenn er meinen GC nutzt

    * Der Grund ist, dass du Konzepte aus anderen Sprachen (Garbage Collector) 1:1 nach C++ übertragen willst, wo Speicherverwaltung aber ganz anders abläuft. C++ hat viel strengere und eindeutigere Besitzverhältnisse als Sprachen mit GC (Speicher ist nicht geteilt, sondern hat meist einen klaren Besitzer). Normalerweise wird der Speicher von dem freigegeben, der ihn anfordert. Mit deinem Versuch verstösst du gegen diese Konvention.

    Den Speicher den ich freigebe, kann man auch nur aus meinem GC anfordern. Also hat den auch kein anderer freizugeben. Das mit dem Sprachübergreifend lassen wir mal. In C++ kann man auch nur Speicher anlegen und wieder freigeben. Und da der Benutzer keinen Speicher anfordet, sondern mein GC ist alles gut.

    * Dadurch rufst du beim Benutzer Verwirrung hervor. Wenn der Benutzer ein einfaches new verwendet, geht er davon aus, den Speicher ohne Probleme mit delete freigeben zu können. Er wird sich wundern, wenn ganz normaler Code einfach abstürzt.

    Beziehe mich auf Punkt 2 - auf die Objekte kann er kein new machen.

    * Gleichzeitig nimmst du dem Anwender mit dem GC die Freiheit, sich Speicher irgendwie zu verschaffen. Er kann den Stack benutzen, den Heap (malloc() aus C), den Freestore (new) oder irgendeinen anderen Allokator. Mit einem automatischen delete in deiner Bibliothek zwingst du ihn zur Verwendung des new-Operators.

    Diese Freiheit behält der Benutzer für jegliche Klassen, die er selbst baut und die nicht zu meinem GC gehören.

    * In C++ benutzt man das RAII-Idiom, um manuelle Speicherverwaltung zu vermeiden. Damit muss gar niemand mehr explizit den Speicher freigeben, sondern die Aufgabe wird an eine Klasse (z.B. Smart-Pointer) delegiert. Damit ist der Grund dafür, überhaupt einen automatischen Freigabemechanismus in deiner Bibliothek einzurichten, hinfällig.

    Auch ich benutze RAII - dennoch wüsste ich gern ob das Objekt auf dem Stack oder auf dem Heap ist.

    Ich benötige diese Info nicht um das Objekt zu löschen, sondern um gewisse Auswertungen zu machen.

    Also meine alte Frage ob ich diese Unterscheidung zwischen Stack und Heap heraus bekommen kann. Ich wüsste auch nicht warum das nicht gehen sollte.


Anmelden zum Antworten