Memory leaks: Ein Leck im Speicher
« | 26 Mar 2019 | »Gerade beim Aufbau von elementaren Datenstrukturen wie Arrays, Listen, Mappings oder Bäumen ist das Thema Speicherverwaltung vorherrschend.
Denn schnell übersieht man einen Pointer, dessen Ziel nicht freigegeben und “vergessen” wird. Bis zum Ende des Prozesses ist dieser Speicher verloren.
Gibt es also ein Fundbüro für diese verlorenen Datenblöcke?
Ja! Und diese heißen Memory-Leak-Detektoren.
In Microsoft’s Visual Studio
ist seit langem ein solcher Detektor im Debug-Modus enthalten. Genutzt werden
kann er durch den Header <crtdbg.h>
und durch den Aufruf der Funktion
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
am Programmstart wird beim Ende automatisch ein Leak-Report der
C-Runtime im Debug-Output-Fenster ausgegeben.
Eine wirkliche Hilfe ist das allerdings meist nicht, den die CRT
kennt nur durch malloc()
(und new
)
allokierte Bereiche, die noch nicht freigegeben wurden. Und man kann im besten
Fall nur die Codezeile zurückverfolgen, wo der malloc()
(oder der new
)
Aufruf platziert wurde.
Bei Bibliotheken hilft das nicht, weil der “Auftraggeber” wo anders sitzt.
Visual Leak Detector
Der Visual Leak Detector löst dieses Problem kostenlos, indem er zu jeder nicht freigegebenen Allokation den Callstack mitspeichert und beim Leak-Report am Ende ausgibt. So kann man zurückverfolgen, welche Funktionshierarchie für die Anforderung des Speichers verantwortlich war und programmatisch dagegen vorgehen.
Seit über 10 Jahren ist der VLD bei mir ein fester Bestandteil in der
Windows und MSVC
Entwicklung. Er wurde mehrmals weiterentwickelt und klinkt
sich inzwischen in die meisten anderen Allokationsroutinen ein, die parallel
zur C-Runtime eigene Heaps benutzen und kann somit auch diese Überwachen.
Address Sanitizer
Für Linux empfahl mir ein guter Kollege einst die Einbindung von libASAN. Und seit ich diesem genialen Vorschlag gefolgt bin, kann ich mir das Leben ohne diese Tool ebenso nicht mehr vorstellen.
Wenn man nämlich sein Programm mit der Option -fsanitize=address
kompiliert,
wird einiges an Instrumentations-Code eingebaut. Läuft das Programm, wird bei
dessen Beendigung ebenso eine Zusammenfassung auf die Konsole geschrieben,
die von Speicherlecks bis Array-Überläufe vieles meldet.
Feineinstellen kann man den Sanitizer über
Environment-Variablen, denn
LSAN_OPTIONS
kann Einstellungen zur Leak-Erkennung beinhalten, wie z.B.:
ASAN_OPTIONS=verbosity=1:malloc_context_size=20
So lassen sich einzelne Features ein- bzw. ausschalten, oder deren Genauigkeit verändern.
Fazit
Ohne Leak-Detektoren gibt es kein Release!
Leaks können immer auftreten und deren Bekämpfung hat hohe Priorität. Von daher sollte auf jeder Plattform eine Möglichkeit zur Leak-Erkennung und -Bekämpfung eingesetzt werden.
Natürlich gibt es noch einige andere Produkte, die eine solche Fehlersuche
erleichtern. Valgrind kennen sicher
viele Linuxianer. Und vor langer Zeit setzte ich beruflich
BoundsChecker unter Windows ein.
Doch inzwischen habe ich festgestellt, dass die beiden genannten
Gratis-Tools eigentlich den Großteil meiner “Bedürfnisse” in der
Entwicklung abdecken und somit sind weitere Tools einfach uninteressant
geworden.
Nachtrag: Eigenbau
Wenn es einem nur um die eigenen malloc()
und new
Aufrufe geht,
kann man sich einen Leak-Detector auch selbst schreiben. Makros
können Aufrufe der beiden Funktionen auf eigenen Code umleiten,
wo in einer globalen Liste alle Allokationspointer gespeichert
werden. Durch die Umleitung von free()
und delete
kann diese
Liste wieder bereinigt werden.
Am Ende braucht man nur noch den Inhalt dieser globalen Liste anzeigen, denn sie beinhaltet alle (noch) nicht freigegebenen Speicherblöcke.
Aber Vorsicht! Fremdbibliotheken, für die solche Makros nicht gelten,
bleiben weiter unsichtbar, wenn man ihre “Strukturen” vergisst
freizugeben, weil sie von der lokalen Umleitung nichts wissen.
Und auch alle Codeblöcke, die systemspezifische andere
Allokationsrountinen nutzen, werden von dieser Kleinimplementierung
nicht abgedeckt.