new- und delete-operator überschreiben
-
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 Standardnew
nicht auch hätte. Dazu könntest Du einfachstd::unique_ptr
bzw.std::shared_ptr
nutzen, 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
unddelete
ü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ürt_NewHeapObject
besser geeignet, da diese Klasse dann die Resourcenverwaltung übernimmt. Alternativ kann man einen wie bereits angeführt einenstd::unique_ptr
oder einenstd::shared_ptr
nutzen und diesemmalloc
undfree
übergeben. Wobei in so einem Fall braucht manmalloc
undfree
nicht mehr, da für diesen Objekttyp einfach auch nurnew
unddelete
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 einermap
.
-
@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 einermap
.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 dermap
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 manvoid* operator new(size_t)
überladen und eben nichtvoid* 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 dochmalloc
undfree
.::operator new
ist nicht die Lösung, da ruft er den selbst implementierten Operator rekursiv auf.
-
Wieso spricht man in diesem Zusammenhang eigentlich von
überladen
und nicht vonüberschreiben
?
Eigentlich ist doch Ersteres eine Funktion mit gleichem Namen aber anderer Signatur - wenn mannew
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
überladen
und nicht vonüberschreiben
?
Eigentlich ist doch Ersteres eine Funktion mit gleichem Namen aber anderer Signatur - wenn mannew
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
anstattc++stdlib.a(new.o)
(o.Ä.) linken soll (oder eben iminline
-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 Dateinew.h
[welche von der ÜE eingebunden wird], aber nicht bei jedem Aufruf vonnew
).Daher wird vom PP mit dem Makro jeder Aufruf von
new
durchnew(__FILE__, __LINE__)
ersetzt (und dann passen die Zeilenangaben auch zum Sourcecode).
Und daher werden dann die überladenen Versionen vomoperator 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 deinenew
Überladung benutzen. Ist ne Quality of Life Funktion, die das Auswerten der Log einfacher macht, weil man sofort sieht, in welcher Funktion dernew
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
-
Man könnte auch
CaptureStackBackTrace
verwenden und die "normalen"operator new
Funktionen ersetzen. Damit bekommt man mehr & bessere Informationen. Natürlich müsste man den dann zur Anzeige noch auflösen, aber dafür gibt's ja die dbghelp Funktionen.
-
__PRETTY_FUNCTION__
ist eine gcc-Erweiterung.Seit C99 bzw. C++11 gibt es
__func__
- dies wird auch vom VS (bzw. MSVC) unterstützt, s.a. Predefined macros (sowie weitere__FUNC...__
-Makros).
-
Hallo an alle Antwortenden,
lieben herzliche Dank für Eure interessanten Beiträge!
Das mit__PRETTY_FUNCTION__
werde ich beizeiten mit dem GCC ausprobieren.
Ansonsten pausiere ich jetzt erstmal bis Januar 22.
Werde mich dann weiter mit dem überladen vom new- und delete-operator beschäftigen und mich an dieser Stelle wieder melden.
Ich wünsche Euch allen schöne Feiertage und einen angenehmen Übergang ins neue Jahr.
Helmut
-
@Th69 sagte in new- und delete-operator überschreiben:
@john: Schau mal in Zeile 25 des Codes vom Eingangsbeitrag!!!
- Das Makro lässt sich nicht mit g++ übersetzen.
- Das Makro bricht gültigen C++ Code!
Nun, hatte ich versucht die Routine
inline
zu deklarieren, aber der nvc++ gibt dazu folgende Warnung heraus."new.h", line 9: warning: declaring a new or delete operator "inline" is nonstandard inline void* operator new (std::size_t s) { ^ "new.h", line 25: warning: declaring a new or delete operator "inline" is nonstandard inline void operator delete (void* p) { ^
Ok, das ist relativ eindeutig. Also den Code nochmals angepasst und in Header und Implementation aufgetrennt.
Es bleibt halt die Frage was man so einer Übung erreichen will. Es gibt üblicherweise Tools, mit denen man die Memory Leaks leicht finden kann, da braucht man da nicht selbst herumzuspielen und versuchen das Rad neu erfinden zu wollen.
// new.h #ifndef NEW_H #define NEW_H #include <new> #include <cstddef> #include <cstdlib> #include <iostream> void* operator new (std::size_t s); void* debug_new (std::size_t s, const char* const str); void* operator new (std::size_t s, const char* const str); void operator delete (void* p); // nur um den Compiler Warnings zu unterdrücken void operator delete (void* p, std::size_t s [[maybe_unused]]); #ifdef DEBUG #define NEW new(__FILE__) #elif defined NEW_NO_COMPILE #define new new(__FILE__) #else #define NEW new #endif #endif
// new.cc #include "new.h" void* operator new (std::size_t s) { std::cout << "überschriebenes new\n"; return malloc(s); } void* operator new (std::size_t s, const char* const str) { return debug_new(s, str); } void* debug_new (std::size_t s, const char* const str) { std::cout << "debug_new " << str << "\n"; return malloc(s); } void operator delete (void* p) { std::cout << "überschriebenes delete\n"; free(p); } // nur um den Compiler Warnings zu unterdrücken void operator delete (void* p, std::size_t s [[maybe_unused]]) { std::cout << "delete Datei: " << __FILE__ << ", Zeile " << __LINE__ << "\n"; free(p); }
// class.h #ifndef CLASS_H #define CLASS_H #include <new> #include <cstddef> #include <iostream> class C { public: void f(); static void* operator new (std::size_t s); static void operator delete (void* p); }; #endif
// class.cc #include "class.h" // Die Klasse C definiert aus einem nicht näher bestimmten Grund einen eigenen operator new. // Wenn man das braucht, gibt es die seit langem die Empfehlung das nicht global zu tun, sondern // in einer Klasse selbst zu machen – siehe z.B. Effective C++ von Scott Meyers. // Da wir das Rad nicht neu erfinden wollen, nutzen wir hier den globalen operator new, mit // weiterem Code der hier als Beispiel nur ausgibt, dass dieses Routine aufgerufen wurde. void* C::operator new (std::size_t s) { std::cout << "C::new\n"; return ::operator new(s); } void C::operator delete (void* p) { std::cout << "C::delete\n"; ::operator delete(p); } void C::f () { std::cout << "C::f()\n"; }
// main.cc #include <iostream> // Wir wollen ja auch die Speicheranforderungen der eigenen Klassen verfolgen. // Deshalb wird new.h vor class.h inkludiert, sonst funktioniert das Verfolgen // der eigenen Klassen nicht. #include "new.h" #include "class.h" int main () { #ifdef DEBUG int* p = NEW int; C* q = NEW C; #else int* p = new int; C* q = new C; #endif delete q; delete p; }
LD=g++ CCC=g++ CCFLAGS=-std=c++17 -Wpedantic -Wall -Wextra -DNEW_NO_COMPILE LDFLAGS= TARGETS=main OBJS=main.o class.o new.o .PHONY: all clean .SUFFIXES: .cc all: $(TARGETS) clean: rm -f $(OBJS) $(TARGETS) main: $(OBJS) $(LD) $(LDFLAGS) $(OBJS) -o main .cc.o: $(CCC) $(CCFLAGS) -c $< -o $@
Nachtrag: Die Dateien fürs Beispiel angepasst, um den Fehler besser hervor zu heben, Makefile hinzugefügt und Kommentare füin den Sourcen hinzugefügt.