new- und delete-operator überschreiben



  • Hallo,
    ich schlage mich jetzt schon eine ganze Weile mit dem Problem herum, den "operator new" und "operator delete" für mehrere Source-Dateien neu zu defineiern.
    Angeregt durch @muazsh in https://www.c-plusplus.net/forum/topic/352761/wie-nach-memory-leaks-suchen/42 und Rainer Grimm https://www.grimm-jaud.de/index.php/blog/operator-new-und-delete-ueberladen habe ich versucht den new- und delete-Operator neu zu implemtieren.

    void* operator new(std::size_t sz,char const* file, int line)
    {  
    	void * t_pNewHeapObject = std::malloc( size );
      
      if ( t_pNewHeapObject )
    	{
        // Meine implementierung
        return t_pNewHeapObject;
    	}
      throw std::bad_alloc{};
    }
    //----------------------------------------------------------------------------
    void* operator new [](std::size_t sz,char const* file, int line)
    {  
    	void * t_pNewHeapObject = std::malloc( size );
      
      if ( t_pNewHeapObject )
    	{
        // Meine implementierung
        return t_pNewHeapObject;
    	}
      throw std::bad_alloc{};
    }
    //----------------------------------------------------------------------------
    #define new new(__FILE__, __LINE__)
    //----------------------------------------------------------------------------
    void operator delete(void* ptr) noexcept
    {
        // Meine implementierung
        std::free(ptr);
        ptr= nullptr;
    }
    

    Es war mir nicht möglich, die neu definierten Operatoren mehrfach zu 'includen'.
    Fehler z.B. in Visul Studio:

    TestClass.obj : error LNK2005: "void * __cdecl operator new(unsigned __int64,char const *,int)" (??2@YAPEAX_KPEBDH@Z) ist bereits in main.obj definiert.
    

    Also eigentlich für mich nutzlos, weil ich ja die neuen Operatoren in mehreren Sourcen nutzen will.
    Ich kann mir einfach nicht vorstellen, dass das Neudefinieren der new- und delete-Operatoren nur für eine Source-Datei möglich sein soll, wo ist mein Fehler?

    Ich habe durch reichlich rumprobieren herausbekommen, dass zumindest im Visual Studio C++ Kompiler die Operatoren 'inline' definiert werden können (ok, ne Warnung gab es schon, ist ja auch nach Standard nicht erlaubt).
    Jetzt habe ich zumindest in Visual Studio die Möglichkeit (mingw geht gar nicht!) die Operatoren in mehreren Source-Dateien einzubinden.
    Das ist aber nicht das, was ich will, also das inline!

    Hat jemand eine Tip für mich, wo mein Denkfehler ist? Wie kann man new- und delete-Opertoren für mehrere Soucen definieren.

    Was mach ich in den Operatoren bzw. was will ich erreichen? In den new-Operatoren verwaltet eine globale 'unordered_map' threadsicher die mit new instantiierten Objekte, im überschriebenen delete-Operator wird das zu löschende Objket wieder aus der Map entfernt. Übrig bleiben die nicht nicht gelöschten.

    Würde für jede Anregungen dankbar sein.



  • Was genau meinst du mit
    @Helmut-Jakoby sagte in new- und delete-operator überschreiben:

    für mehrere Source-Dateien neu zu defineiern.

    ?

    Die Fehlermeldung ist doch eindeutig und weist auf One Definition Rule (ODR) hin, also daß diese nur in einer Source-Datei definiert werden dürfen (warum Rainer Grimm diese jeweils in einer Headerdatei definiert, ist schon eigenartig - für einen C++ Profi).
    Diese globalen Definitionen gelten dann selbstverständlich für alle Source-Dateien des Programms (also zur Laufzeit).

    Edit: Seit C++17 sollte aber auch inline so funktionieren, daß kein Linkerfehler mehr erscheint.

    Sehe gerade: das #define new mußt du dagegen in eine Headerdatei auslagern (da es ja vom Präprozessor abgehandelt werden muß) - diese dann aber nicht von der Source-Datei mit den Operatoren einbinden. ;- )



  • Hallo @Th69 ,
    sorry wegen der späten Rückmeldung; danke für Deine Antwort
    Habe u.A. übersehen, dass die Parameterübergabe 'char const' eigentlich 'const char' sein müsste. Mit Visual Studio bin ich schon gut vorangekommen, Speicherleaks kann ich auch in meinen eigenen DLLs ermitteln, obwohl ich noch nicht genau weiß, was passiert wenn andere eingebundene Bibliotheken auch den new-operator überschreiben.
    Ist jedenfalls nach meine Recherchen schon öfters versucht worden.
    Werde mein Ergebnis gerne hier melden, wenn ich fertig bin.



  • @Helmut-Jakoby sagte in new- und delete-operator überschreiben:

    Habe u.A. übersehen, dass die Parameterübergabe 'char const' eigentlich 'const char' sein müsste.

    const char und char const ist gleichbedeutend. Wo das const steht wird erst relevant wenn Zeiger ins Spiel kommen.

    const int* ist das gleiche wie int const*: ein Zeiger auf int const.
    int* const ist dann was anderes: ein const Zeiger auf einen int.
    Und int const* const wäre dann ein const Zeiger auf int const.



  • Läuft es denn jetzt bei dir?

    @Helmut-Jakoby sagte in new- und delete-operator überschreiben:

    Habe u.A. übersehen, dass die Parameterübergabe 'char const' eigentlich 'const char' sein müsste.

    Da gibt es keinen Unterschied dazwischen (beide sind semantisch äquivalent).



  • Hi @Th69 ,
    soweit gut unter Visual Studio.
    Grob umrissen. Ich habe eine globale singleton class mit einer unordered_map, in die der überschriebene new-operator das instantiierte Objekt mit Auskunft von DateiName und Zeilennummer (in der das Objekt instantiiert wird) rein stellt. Der überschriebene delete-operator entfernt den jeweiligen Eintrag. Das alles abgesichert durch einen mutex (weil ich mir nicht sicher bin, ob das Einfügen bzw. entfernen aus einer Map atomar ist).
    Dass alles ist in einer DLL. Nur die operator-deklarationen müssen in die cpp-dateien eingebunden sein, wo gewünscht. Die operator-definitionen müssen in jedes Projekt.
    In der jeweiligen main muss natürlich die singleton class instantiiert und ggf. abgefragt werden.



  • Mir ist nicht so klar, weshalb Du hier überhaupt new und delete überschreibst. Dazu warum nutzt Du malloc? malloc hat keine Eigenschaften, die das Standard new nicht auch hätte. Dazu könntest Du einfach std::unique_ptr bzw. std::shared_ptrnutzen, und dem einfach die Zeiger auf malloc und free übergeben.



  • Hallo @john-0 ,
    wenn Du meinst, dass ich smart-pointer nutzen soll, dass mach ich bei meinen Projekten. Aber alter Code mit nichtaufgeräumten Speicher interessiert mich gerade.
    Was Du mit malloc meinst, verstehhe ich gerade nicht.



  • @Helmut-Jakoby sagte in new- und delete-operator überschreiben:

    Hallo @john-0 ,
    wenn Du meinst, dass ich smart-pointer nutzen soll, dass mach ich bei meinen Projekten. Aber alter Code mit nichtaufgeräumten Speicher interessiert mich gerade.
    Was Du mit malloc meinst, verstehhe ich gerade nicht.

    Das hier meine ich.

    void* operator new(std::size_t sz,char const* file, int line)
    {  
    	void * t_pNewHeapObject = std::malloc( size );
      
      if ( t_pNewHeapObject )
    	{
        // Meine implementierung
        return t_pNewHeapObject;
    	}
      throw std::bad_alloc{};
    }
    

    Wozu das ganze? Das ist so richtig C-Style und nutzt die Vorteile von C++ mal überhaupt nicht. Wenn man von C auf C++ umstellt, sollte man das gleich richtig machen und RAII nutzen. Wenn man nur new und delete überlädt, dann bleibt ein Großteil der Vorteile von C++ vollkommen ungenutzt. Meiner Meinung nach wäre man hier mit eine Wrapper Klasse für t_NewHeapObject besser geeignet, da diese Klasse dann die Resourcenverwaltung übernimmt. Alternativ kann man einen wie bereits angeführt einen std::unique_ptr oder einen std::shared_ptr nutzen und diesem malloc und free übergeben. Wobei in so einem Fall braucht man malloc und free nicht mehr, da für diesen Objekttyp einfach auch nur new und delete nutzen kann.

    Weshalb eine Klasse?
    Den Code, den ich hier von Dir zitiert habe, macht im Grunde nichts weiter als den Konstruktor einer Klasse zu sein. Also warum nicht gleich eine eigene Klassen schreiben?



  • Hallo @john-0 ,
    was ich versuche zu implementieren, ist den new-operator zu überschreiben. Ich versuche nicht alten C Code auf C++ Code umzustellen.
    Der überschriebene new-operator macht anstelle des Kommentars "// Meine implementierung" einiges. Er stellt das mit std::malloc erstellte Objekt mit der Adresse und dem Ort (filename und line) in eine Map.
    Der überschriebene delete-operator entfernt das zu löschende Objekt wieder aus der Map.
    Der Kommentar "// Meine implementierung" sollte kennzeichnen, dass hier etwas implementiert ist. Ich wollte nicht alles hier vollschreiben.
    Btw. Der new[]-operator ist gar nicht notwendig, wird automatisch aufgerufen.
    Im Augenblick teste ich den ganzen Krempel und es tut erst mal soweit was ich will.

    PS. Wie griegt man das hier mit der roten Schrift hin?



  • @Helmut-Jakoby
    Wörter in drei ` (Backticks) einfassen.



  • Je ein Backtick (mit drei werden mehrzeilige Codeblöcke gekennzeichnet).

    @john-0 hat anscheinend den Zweck nicht verstanden, nämlich das Ersetzen aller new Ausdrücke im Code durch weitere Angaben (FILE, LINE) zum Speichern in einer map.



  • @Helmut-Jakoby sagte in new- und delete-operator überschreiben:

    Hallo @john-0 ,
    was ich versuche zu implementieren, ist den new-operator zu überschreiben. Ich versuche nicht alten C Code auf C++ Code umzustellen.

    Ist die Zielsetzung nur das zu üben? Wie gesagt, für realen Code empfiehlt sich das halt nicht.

    Fürs Markierung der Befehle nutzt man ein Backtick.



  • @Th69 sagte in new- und delete-operator überschreiben:

    @john-0 hat anscheinend den Zweck nicht verstanden, nämlich das Ersetzen aller new Ausdrücke im Code durch weitere Angaben (FILE, LINE) zum Speichern in einer map.

    Falsch, genau das wird ja nicht gemacht! Es wird nämlich nicht void* operator new(size_t) überladen.



  • @john-0: Hast du den Eingangsbeitrag nicht gelesen? Es geht um das Finden von Speicherlecks (daher die map), so wie es z.B. auch AFX bzw. MFC im Debug-Modus machen, d.h. bei Programmende dann alle noch in der map befindlichen Adressen + Dateiname sowie Zeilenangabe auszugeben.



  • @Th69 sagte in new- und delete-operator überschreiben:

    @john-0: Hast du den Eingangsbeitrag nicht gelesen?

    Hast Du das mit dem Überladen von operator new verstanden? Will man Speicherlecks finden muss man void* operator new(size_t) überladen und eben nicht void* operator new(std::size_t sz,char const* file, int line). Wenn man die zweite Variante wählt müsste man den Sourcecode verändern!

    Ok, um hier einen Ansatzpunkt zu bringen ein simpler Header wie man das umsetzen kann

    // new.h
    #include <new>
    #include <cstddef>
    #include <cstdlib>
    #include <iostream>
    
    inline void* operator new (std::size_t s) {
        std::cout << "new    Datei: " << __FILE__ << ", Zeile " << __LINE__ << "\n";
    
        return malloc(s);
    }
    
    inline void operator delete (void* p) {
        std::cout << "delete Datei: " << __FILE__ << ", Zeile " << __LINE__ << "\n";
    
        free(p);
    }
    
    inline void operator delete (void* p, std::size_t s [[maybe_unused]]) {
        std::cout << "delete Datei: " << __FILE__ << ", Zeile " << __LINE__ << "\n";
    
        free(p);
    }
    

    In C++ 98 konnte man auch noch auf new der Implementation zugreifen in dem man ::new benutzte, aber das mag mittlerweile der Compiler irgend wie nicht mehr. Deshalb doch malloc und free. ::operator new ist nicht die Lösung, da ruft er den selbst implementierten Operator rekursiv auf. 😞



  • Wieso spricht man in diesem Zusammenhang eigentlich von überladenund nicht von überschreiben?
    Eigentlich ist doch Ersteres eine Funktion mit gleichem Namen aber anderer Signatur - wenn man new neu schreibt, will man aber doch üblicherweise die gleiche Signatur wie das Original, also überschreiben🤔



  • @Belli sagte in new- und delete-operator überschreiben:

    Wieso spricht man in diesem Zusammenhang eigentlich von überladenund nicht von überschreiben?
    Eigentlich ist doch Ersteres eine Funktion mit gleichem Namen aber anderer Signatur - wenn man new neu schreibt, will man aber doch üblicherweise die gleiche Signatur wie das Original, also überschreiben🤔

    Ich würde argumentieren, dass überschrieben so wie ich das verstehe, ausserhalb von abgeleiteten Klassen aus ODR-Gründen ohnehin nicht geht. Was hier gemacht werden soll, würde ich persönlich eher ersetzen nennen. Ich ersetze die Implementierung in der Standardbibliothek durch meine eigene. z.B. indem ich dem Linker verklickere, dass er gegen my-new.o anstatt c++stdlib.a(new.o) (o.Ä.) linken soll (oder eben im inline-Fall mit einer anderen geeigneten Methode).



  • @john: Schau mal in Zeile 25 des Codes vom Eingangsbeitrag!!!

    Bei deinem Ansatz wird nur einmalig (je Übersetzungseinheit) __FILE__ und __LINE__ vom Präprozessor gesetzt (und zwar bezogen auf die Zeile 8 der Datei new.h [welche von der ÜE eingebunden wird], aber nicht bei jedem Aufruf von new).

    Daher wird vom PP mit dem Makro jeder Aufruf von new durch new(__FILE__, __LINE__) ersetzt (und dann passen die Zeilenangaben auch zum Sourcecode).
    Und daher werden dann die überladenen Versionen vom operator new aufgerufen - Magic!

    PS: Dies wird auch im 2. Teil des Artikels von Rainer Grimm so erklärt: Operator new und delete überladen: Teil 2
    (Daher war der Link nur auf den 1. Teil vllt. irreführend?!)



  • @Belli sagte in new- und delete-operator überschreiben:

    Wieso spricht man in diesem Zusammenhang eigentlich von überladenund nicht von überschreiben?

    Überladen (overload) nennt man es wenn man mehrere Funktionen mit dem selben Namen aber unterschiedlichen Parameterlisten macht. Welche aufgerufen wird hängt dann halt von der Parameterliste ab.

    Überschreiben (override) nennt man es wenn man eine Funktion mit dem selben Namen und der selben Parameterliste in einer abgeleiteten Klasse definiert. Welche aufgerufen wird hängt dann davon ab welchen Typ das Objekt hat, bzw. auch ob der Funktionsname beim Aufruf "fully qualified" war (Klasse::Funtion).



  • @Helmut-Jakoby
    Wenn dein Compiler das Makro __PRETTY_FUNCTION__ unterstützt würde ich das Parameter für deine newÜberladung benutzen. Ist ne Quality of Life Funktion, die das Auswerten der Log einfacher macht, weil man sofort sieht, in welcher Funktion der new Aufruf passiert ist. Ich habe sowas bei mir in einem TRACE Makro und mag nicht mehr ohne 😉

    #ifdef __PRETTY_FUNCTION__
     #define new new(__FILE__, __PRETTY_FUNCTION__, __LINE__ )
    #else
     #define new new(__FILE__, __LINE__)
    #endif

Anmelden zum Antworten