Push-Nachrichten von MacTechNews.de
Würden Sie gerne aktuelle Nachrichten aus der Apple-Welt direkt über Push-Nachrichten erhalten?
Forum>Entwickler>Memory leak durch Operator "new" (C++)

Memory leak durch Operator "new" (C++)

andreas_g
andreas_g22.06.2220:19
Liebe Community,

ich bekomme von Xcode ein Speicherleck in folgender Datei gemeldet:

Offensichtlich entsteht das Leck beim Aufruf von map_banks() in der Funktion mem_init(). Ursache dürfte der Operator new sein.

Hat jemand eine Idee, was hier schief läuft und wie das zu beheben wäre?
0

Kommentare

Mendel Kucharzeck
Mendel Kucharzeck22.06.2220:44
Wenn du irgendwo Speicher anforderst, muss der auch wieder vom Programm selbst freigegeben werden. Kannst du mal einen Breakpoint auf die Stelle setzen, in welcher der Speicher eigentlich wieder freigegeben werden sollte? Vllt kommst du daran gar nicht vorbei.
0
andreas_g
andreas_g22.06.2220:59
Wie würde ich den Speicher in dieser Konstellation wieder freigeben?
0
strife22.06.2222:04
Du müsstest sämtliche Rückgabewerte von new() in der mem_init() Funktion in Variablen speichern und zu gegebener Zeit mittels delete() wieder freigeben.

Da sich der Original Author das aber offensichtlich nicht für wichtig hielt, weil der allozierte Speicher wahrscheinlich über die gesamte Laufzeit des Programms benötigt wird, würde ich mir die Arbeit sparen und einfach die Speicherwarnung in Xcode ignorieren.
+1
AidanTale22.06.2222:22
In deinem Destruktor solltest du das tun:
NextDimension::~NextDimension()

Das map_banks() ruft im Kode nd_put_mem_bank() auf in die der dynamische Speicher weitergereicht wird. Ich habe diese Methode auf die Schnelle nicht gefunden, aber sie sollte hoffentlich den jeweiligen Zeiger irgendwo in den privaten Daten deiner Klasse speichern, so dass alle Zeiger im Destruktor wieder freigegeben werden können.

Aber vermutlich hat strife recht und das Objekt dieser Klasse wird niemals abgeräumt ... .
0
gfhfkgfhfk23.06.2208:40
andreas_g
Wie würde ich den Speicher in dieser Konstellation wieder freigeben?
Du solltest Dich sehr dringend mit RAII, Rule of five und SmartPointer auseinandersetzen. Wenn man C++ Programme sauber entwirft, gibt man nie von Hand Speicher frei. Bei wenigen Klassen könnte es im Destruktor notwendig sein. Wenn Du Speicher irgend wo anders freigeben musst, läuft etwas schief. Aber bei den vielen news in Deinem Code sieht das nicht danach aus, dass Du die üblichen Regeln für C++ einhälst. Um genau beurteilen zu können was schief läuft müsste man den kompletten Code von der NextDimension Klasse sehen.
+1
andreas_g
andreas_g23.06.2221:37
Danke für alle Rückmeldungen! Ich werde am Wochenende versuchen, das zu verstehen und eine Lösung zu finden. Ich programmiere normalerweise nur in C. Daher sind mir einige Dinge bei C++ noch ziemlich fremd.
0
ssb
ssb24.06.2210:09
Also zu glauben, dass man Speicher bei C++ nicht freigeben muss, weil der Compiler schon wisse, was zu tun sei, halte ich für optimistisch. Selbst beim gut gemachten ARC für ObjC klappt das nicht immer.
Auch in C++ (ich habe mir den Code nicht angesehen) gibt es mittlerweile entsprechende Konstrukte, bei denen der Compiler darüber entscheidet, wann Speicher freigegeben werden kann und muss. Bei C++ ist das nur besonders unübersichtlich, weil die Struktur von C++ an sich (in meinen Augen) unübersichtlich ist. ObjC ist da klarer.
Daher mein Vorschlag: wenn irgendwo "new" benutzt wird, dann sollte man auch immer "delete" benutzen. Übrigens darf man "new/delete" und "alloc/free" nicht mischen, das kann je nach Platform ganz seltsame Probleme verursachen.

Egal - lass das Programm mal mit dem Address-Sanitizer laufen. Der sollte dir dann sagen können, wo der Speicher allokiert wurde. Auch der Static Analyzer kann da hilfreich sein. Xcode bietet da gute Optionen. Auch Instruments kann helfen.

Ein Speicherleck einfach stehen zu lassen ist aber definitiv falsch gedacht. Das ist schlechter Programmierstil den man sich gar nicht erst angewöhnen sollte.
Ich nutze auch meist C und habe mir angewöhnt, Pointer immer auf NULL zu initialisieren und nach dem free() den Pointer immer wieder auf NULL zu setzen. Am Ende einer Funktion habe ich dann für jeden Pointer "if(ptr) free(ptr);" stehen. Sollte dieser Code total unnötig sein, wird er vom Compiler weg optimiert.
+1
gfhfkgfhfk24.06.2213:42
ssb
Also zu glauben, dass man Speicher bei C++ nicht freigeben muss, weil der Compiler schon wisse, was zu tun sei, halte ich für optimistisch.
Es ist bei modernen C++ (d.h. ab C++11) Code die Regel und nicht die Ausnahme, da man selbst keinerlei Speicherallokationen mehr durchführt! Das Problem wird an die Container-Klassen und die Pointer-Klassen delegiert, die das ganze für normalen Programmcode problemlos weg abstrahieren. Als normaler Entwickler muss man sich nur um die Lebensdauer der Objekte kümmern. Wichtig ist, man nutzt keinen besitzenden nackten Zeiger mehr in C++, dafür gibt es die SmartPointer-Klassen, die bei Bedarf die nackten Zeiger für C APIs herausgeben. Es gibt wenige Fälle, bei denen man noch selbst Hand anlegen muss, aber dann sollte man die Klassen nach der Rule-of-Five korrekt implementieren.

Bei einem Emulator wie im gegeben Beispiel könnte das so sein, dass man noch Klassen entwerfen muss, die ihre Resourcen selbst verwalten. Allerdings sieht das kurze Codebeispiel so aus, als ob die Speicherverwaltung noch auf C Niveau sei, und nur malloc mit new und free mit delete ersetzt wurde. Aus C++ Sicht ist das ein Stil zu programmieren, den man seit Mitte der 1990er nicht mehr nutzt.
ssb
Selbst beim gut gemachten ARC für ObjC klappt das nicht immer.
Das ist auch kein Wunder, da die Speicherverwaltung bei Objective-C irgend wo zwischen C with Classes und der ersten ISO Norm von C++ anzusiedeln ist. Es fehlt bei Objective-C an so vielem, um Speicher sicher verwalten zu können. Das macht die Sprache deutlich einfacher als C++, aber sorgt andererseits für Probleme. Der ARC ist auf dem Niveau von der ersten funktionierenden SmartPointer Klasse von C++ (Boost shared_ptr um das Jahr 2000). Die Erfahrungen mit dieser SmartPointer-Klasse hat dann zu den Änderungen hinzu C++11 geführt. Will man eine bessere Speicherverwaltung als mit C++ kann man aktuell nur Ada oder noch besser Rust empfehlen.
+2
ssb
ssb24.06.2219:09
Naja - wenn man C++11 vertraut und vor Allem denen, deren Code man verwendet - mag das theoretisch funktionieren. Theoretisch gibt es auch keinen Unterschied zwischen Theorie und Praxis, aber in der Praxis...

Einiges in C++ ist gut gemeint, gut gemacht ist da vieles nicht mehr. Wenn jemand nicht sauber mit Heap-Allocations umgehen kann, dann sollte er es einfach lassen oder lernen. Dafür gibt es dann eben die Code Sanitizer. Ich programmiere seit 40 Jahren in C (und anderen Sprachen) und sich um den Speicher nicht zu kümmern ist einfach schlechter Stil. Das ist wie wild campen und seinen Dreck nicht aufräumen - das Framework wird schon einen Putztrupp schicken. Ein Framework, dass den Entwickler von der Speicherverwaltung "ent-sorgt", führt dann zu sorglosem Umgang.
Es mag aber auch dem geschuldet sein, dass ich für viele meiner Aufgaben eben Pointer brauche, um auf die Rohdaten zugreifen zu können. Daher bleibe ich bei C - und wenn man es richtig macht, gibt es auch keine Leaks oder ähnliche Fehler.

Ansonsten ist C++ für mich nur eine Objekt-orientierte prozedurale Sprache, weil am Ende doch alles statisch verlinkt wird. Echte Objekte wie bei Objective-C gibt es nicht. Mag sehr akademisch sein, aber ich finde es "schöner" wenn sich Objekte Nachrichten schicken, was das eine gerne vom anderen hätte. Smalltalk-Like eben. Aber dahinter steckt eine ganz andere Philosophie und das hat mit dem Speichermanagement erst einmal nichts zu tun. Auch Swift empfinde ich da als großen Rückschritt und die Erfahrung zeigt, dass das Ziel der "sicheren" Sprache eben nicht erreicht wird. Ich glaube mit Steve Jobs hätte Swift nicht diesen Stellenwert erreicht.
0
gfhfkgfhfk24.06.2220:51
ssb
Naja - wenn man C++11 vertraut und vor Allem denen, deren Code man verwendet - mag das theoretisch funktionieren. Theoretisch gibt es auch keinen Unterschied zwischen Theorie und Praxis, aber in der Praxis...
Die Erfahrung zeigt, dass der C++ Weg funktioniert und der von C eindeutig nicht. Die Bug-Meldungen sind da sehr eindeutig. Man kann das alles mit geeigneten Methoden bekämpfen, aber es ist besser, wenn das Problem gar nicht erst auftritt. Dazu kommt, dass in C++ die Fehlersuche viel einfacher ist. Wenn etwas leaked, dann findet sich der Fehler schnell in der betreffenden Klasse, weil die Rule-of-Five nicht korrekt umgesetzt wurde.
ssb
Ansonsten ist C++ für mich nur eine Objekt-orientierte prozedurale Sprache, weil am Ende doch alles statisch verlinkt wird.
C++ und Objective-C unterscheiden sich gar nicht wie die Programme gelinkt werden. Neue Klassen werden immer zum Compilezeitpunkt erzeugt und niemals während der Laufzeit, man kann später erzeugten Code nur per Shared Object per dlopen (macOS nennt die Funktion anders) nachladen. So dass auch später erzeugter Code immer durch den Compiler laufen muss.

C++ übersetzt Methodenaufrufe je nach dem was für ein Methodenaufruf es ist unterschiedlich. Nehmen wir den virtuellen Methodenaufruf (der ist am besten mit dem Objective-C Methodenaufruf vergleichbar) wird dieser so übersetzt, dass z.B. die dritte Methode des Objekts während der Laufzeit aufgerufen wird. Und zwar wird für jedes Objekt immer die dritte Methode aufgerufen an der Stelle im Programmcode. Hat man jetzt ein Objekt bei dem aber die 10. Methode passen würde, dann konnte man lange Zeit in c++ das nicht aufrufen und musste von Hand einen Wrapper dazwischen programmieren, der den Aufruf anpasst. Das muss man mittlerweile nicht mehr von Hand machen, es gibt dafür in der Standardlibrary Template Klassen, die das automatisch machen.

Objective-C löst das Problem anders, es fragt zur Laufzeit, ob das Objekt eine Methode hat, die zum Methodenaufruf passt und ruft dann die Methode auf. Der Vorteil ist, dass ist flexibler als der C++ Weg, aber es ist auch sehr viel langsamer. Ich hatte mal dazu ein Testprogramm geschrieben, Objective-C Aufrufe brauchen gegenüber C++ virtuelle Methodenaufrufe ca. Faktor 1000 mehr Zeit. Selektoren helfen dabei nur unwesentlich. Wesentlich ist hierbei, dass es keine neuen Klassen während der Laufzeit geben kann, weder in C++ noch Objective-C. Jeder Code muss zuerst durch den Compiler und kann bestenfalls als PlugIn nachgeladen werden. Wenn ich aber den Compiler ohnehin nutzen muss, kann dieser gleich das Problem des Methodenaufrufs erschlagen und man erhält so sehr viel schneller Programmcode.
ssb
Aber dahinter steckt eine ganz andere Philosophie
Ich halte das für eine sehr engstirnige Definition von Objektorientierung, die z.B. die von CLOS gar nicht abdeckt. Nur weshalb sollte CLOS keine OOP Sprache sein? Bei Multimethoden ergibt die Sprachrelegung man schickt einem Objekt eine Nachricht keinerlei Sinn, da die Auswahl der Methode von mehreren Objekten abhängt.
0
ssb
ssb25.06.2209:51
Da hast du das dynamic late binding scheinbar nicht verstanden. Man kann sehr wohl Klassen zur Laufzeit erweitern (natürlich nicht mit Source, sondern mit kompiliertem Code) oder verändern. Damit habe ich mich ziemlich gründlich beschäftigt (weil ich die ObjC-Runtime zerlegt habe).

Du kannst sogar einer Klasse zur Laufzeit eine neue Methode geben oder eine Methode überschreiben. Dabei wird diese Änderung nicht nur für neue Instanzen wirksam sondern auch für bereits existierende Instanzen und sogar an abgeleitete Klassen und deren existierenden oder zukünftigen Instanzen. Änderst du zum Beispiel NSObject, die Basisklasse, dann erhalten alle Klassen und deren Instanzen diese Änderung.
Das geht in C++ und anderen prozeduralen Sprachen nicht, außer du verbiegst Pointer, was heutzutage nicht mehr so einfach ist.

Aber wie gesagt, dass ist eine teils akademische Frage und teils eine Frage der Philosophie. Wenn C++11 es jetzt endlich geschafft hat, das in den Griff zu bekommen - fein, hat dann aber auch lange genug gedauert.
0

Kommentieren

Diese Diskussion ist bereits mehr als 3 Monate alt und kann daher nicht mehr kommentiert werden.