Malloc Überprüfungen
Zwei Blog Einträge die ich kürzlich gelesen habe veranlassten mich dazu ein weit verbreitetes Problem in der Softwareentwicklung kritisch zu hinterfragen. Ich empfehle jedem Interessierten sich die Einträge und deren Verweise durchzulesen, dennoch fasse ich einige Punkte zusammen die mich selbst sehr überrascht haben.
Worum geht es denn überhaupt? Wenn man anfängt C zu lernen und zum Thema Speicherverwaltung übergeht wird immer darauf hingewiesen, dass die Funktion malloc() welche Speicher reserviert in Ausnahmefällen auch NULL zurückliefern kann. Dieser Ausnahmefall tritt auf, wenn das System keinen Speicher mehr hat, sagt man. Man solle also immer brav den Rückgabewert von malloc(), realloc() und calloc() auf NULL überprüfen damit man keine Speicherverletzung, also den Zugriff auf nicht vorhandenen Speicher produziert in welchem Fall das Programm abstürzen würde. Mehr dazu später.
Sehr überrascht hat es mich zu erfahren, dass ein scheiterndes malloc() gar nicht die Situation des aufgebrauchten Speichers beschreibt. Vielmehr bedeutet es, dass der Adressraum nicht ausreicht um die geforderte Menge Speicher zu adressieren. Bei heutigen 64bit Systemen ist dies praktisch unmöglich. Selbst wenn ich alle meine Festplatten und Speichermedien in einen 64bit Adressraum abbilde wäre noch reichlich Adressraum vorhanden und malloc() würde nicht fehlschlagen. Bei 32bit Programmen die leicht über die Grenze von 2G kommen können wie beispielsweise rechen- und speicherintensiven Spielen ist die allerdings ein recht akutes Problem. Der Umstieg auf 64bit Systeme schafft Abhilfe und ist zumindest im Windows und Spieleumfeld lange überfällig.
Was passiert denn wirklich im Falle von erschöpftem Speicher, der fälschlicherweise als Grund für ein fehlschlagendes malloc() angenommen wird? Zumindest in einer Linux Umgebung schaltet sich der sogenannte OOM Killer ein. Dieser tötet ganz einfach einen beliebigen Prozess auf dem System um sich dessen Speicher zu holen. Bevor der OOM Killer allerdings überhaupt aktiv wird, wird bereits der Großteil der Programme auf den swap ausgelagert, wodurch die Performance vor allem eines Desktops sowieso in den Keller geht.
Weiters wird erwähnt, dass die Überprüfung des malloc Rückgabewertes meist sehr entwicklungsintensiv ist und in vielen Fällen gar nicht getestet wird. Bei einem Fehlschlag wird das Programm meist sowieso beendet. Ob dies nun durch einen Speicherzugriffsfehler geschieht oder weil man selbst das Programm beendet ist in dem Fall egal, das Ergebnis ist das selbe.
Eine weitere interessante Überlegung die ich vor längerer Zeit gelesen habe und erwähnenswert ist: malloc() macht nur einen geringen Teil von Speicherreservierung aus. Viel öfter wird durch Funktionsaufrufe ein neuer Stack angelegt und Platz für lokale Funktionsvariablen reserviert. Würde in solch einem Fall der Speicher aus gehen hätte man sowieso keine Möglichkeit etwas zu retten und das Programm stürzt sowieso ab. Und solche implizite Speicherreservierung tritt im Laufe eines Programmes viel öfter auf als die explizite Reservierung mittels malloc().