Memory leaks: Ein Leck im Speicher

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.