Anfang 2018 erschien in unserem Blog eine Reihe von Artikeln, die sich der sechsten Überprüfung des Quellcodes des Chromium-Projekts widmeten. Der Zyklus enthält 8 Artikel, die Fehlern und Empfehlungen zu ihrer Vorbeugung gewidmet sind. Zwei Artikel haben heftige Diskussionen ausgelöst, und bisher erhalte ich selten Kommentare zu den darin behandelten Themen. Vielleicht sollten einige zusätzliche Erklärungen gegeben werden und, wie sie sagen, das i punktieren.
Ein Jahr ist vergangen, seit eine Reihe von Artikeln verfasst wurde, die der nächsten Überprüfung der Quellcodes des Chromium-Projekts gewidmet sind:
- Chrom: sechster Projektcheck und 250 Bugs
- Wunderschönes Chrom und ungeschicktes Memset
- brechen und durchfallen
- Chrom: Speicherlecks
- Chrom Tippfehler
- Chrom: Verwenden ungenauer Daten
- Warum ist es wichtig zu überprüfen, was die Malloc-Funktion zurückgegeben hat?
- Chrom: andere Fehler
Artikel über
Memset und
Malloc haben und
haben Diskussionen ausgelöst, die mir seltsam erscheinen. Anscheinend gab es einige Missverständnisse aufgrund der Tatsache, dass ich meine Gedanken nicht klar formuliert hatte. Ich beschloss, zu diesen Artikeln zurückzukehren und einige Klarstellungen vorzunehmen.
Memset
Beginnen wir mit dem Artikel über
Memset , denn hier ist alles einfach. Es gab Kontroversen darüber, wie Strukturen am besten initialisiert werden können. Viele Programmierer haben geschrieben, dass es besser ist, eine Empfehlung zu geben, nicht zu schreiben:
HDHITTESTINFO hhti = {};
Und so:
HDHITTESTINFO hhti = { 0 };
Argumente:
- Das Konstrukt {0} ist beim Lesen von Code leichter zu erkennen als {}.
- Das Konstrukt {0} ist intuitiver als {}. Das heißt, 0 legt nahe, dass die Struktur mit Nullen gefüllt ist.
Dementsprechend wird mir angeboten, dieses Beispiel der Initialisierung im Artikel zu ändern. Ich bin mit den Argumenten nicht einverstanden und habe nicht vor, den Artikel zu ändern. Jetzt begründe ich meine Position.
Über Sichtbarkeit. Ich denke, das ist eine Frage des Geschmacks und der Gewohnheit. Ich glaube nicht, dass das Vorhandensein von 0 in geschweiften Klammern die Situation grundlegend verändert.
Aber mit dem zweiten Argument stimme ich überhaupt nicht überein. Ein Datensatz wie {0} gibt einen Grund, den Code falsch zu verstehen. Sie können beispielsweise berechnen, dass alle Felder in Einheiten initialisiert werden, wenn Sie 0 durch 1 ersetzen. Daher ist diese Art des Schreibens eher schädlich als nützlich.
Der PVS-Studio-Analysator hat sogar eine verwandte Diagnose
V1009 zu diesem Thema, deren Beschreibung ich jetzt geben werde.
V1009. Überprüfen Sie die Array-Initialisierung. Nur das erste Element wird explizit initialisiert.Der Analysator hat einen möglichen Fehler festgestellt, da beim Deklarieren eines Arrays der Wert nur für ein Element angegeben wird. Somit wird der Rest der Elemente implizit auf Null oder den Standardkonstruktor initialisiert.
Betrachten Sie ein Beispiel für verdächtigen Code:
int arr[3] = {1};
Vielleicht hat der Programmierer erwartet, dass
arr aus einer Einheit besteht, aber das ist nicht so. Das Array besteht aus den Werten 1, 0, 0.
Der richtige Code lautet:
int arr[3] = {1, 1, 1};
Eine solche Verwirrung kann aufgrund der Ähnlichkeit mit dem Konstrukt
arr = {0} auftreten , das das gesamte Array auf Nullen initialisiert.
Wenn ähnliche Designs in Ihrem Projekt aktiv verwendet werden, können Sie diese Diagnose deaktivieren.
Es wird auch nicht empfohlen, die Sichtbarkeit des Codes zu vernachlässigen.
Beispielsweise wird der Code zum Codieren von Farbwerten wie folgt geschrieben:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff };
Dank der impliziten Initialisierung sind alle Farben korrekt eingestellt, aber es ist besser, den Code klarer umzuschreiben:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 };
malloc
Bevor Sie weiterlesen, bitte ich Sie, den Inhalt des Artikels "
Warum ist es wichtig zu überprüfen, was die Malloc-Funktion zurückgegeben hat " zu
aktualisieren . Dieser Artikel hat viel Diskussion und Kritik hervorgerufen. Hier sind einige der Diskussionen:
reddit.com/r/cpp ,
reddit.com/r/C_Programming ,
habr.com . Gelegentlich schreiben sie mir jetzt per Post über diesen Artikel.
Der Artikel wird von den Lesern zu folgenden Punkten kritisiert:
1. Wenn malloc NULL zurückgegeben hat , ist es besser, das Programm sofort zu beenden, als eine Reihe von ifs zu schreiben und zu versuchen, den Speichermangel irgendwie zu behandeln, weshalb es oft unmöglich ist, das Programm trotzdem auszuführen.Ich habe bis zum letzten Mal überhaupt nicht angerufen, um mich mit den Folgen eines Speichermangels zu befassen und den Fehler immer höher zu werfen. Wenn es zulässig ist, dass eine Anwendung ohne Vorwarnung heruntergefahren wird, dann sei es so. Dazu ist unmittelbar nach
malloc oder der Verwendung von
xmalloc nur eine Prüfung erforderlich (siehe nächster Absatz).
Ich protestierte und warnte vor dem Fehlen von Überprüfungen, weshalb das Programm weiterhin "so funktioniert, als wäre nichts passiert". Das ist ganz anders. Dies ist gefährlich, da es zu undefiniertem Verhalten, Datenbeschädigung usw. führt.
2. Nicht über die Lösung gesprochen, die darin besteht, Funktions-Wrapper zu schreiben, um Speicher zuzuweisen, gefolgt von einer Überprüfung oder der Verwendung vorhandener Funktionen wie xmalloc .Ich stimme zu, ich habe diesen Moment verpasst. Beim Schreiben eines Artikels habe ich einfach nicht darüber nachgedacht, wie ich die Situation beheben kann. Für mich war es wichtiger, dem Leser die Gefahr eines Mangels an Verifikation zu vermitteln. Wie man einen Fehler behebt, ist bereits eine Frage des Geschmacks und der Implementierungsdetails.
Die
xmalloc- Funktion
ist nicht Teil der C-Standardbibliothek (siehe "
Was ist der Unterschied zwischen xmalloc und malloc? "). Diese Funktion kann jedoch in anderen Bibliotheken deklariert werden, z. B. in der GNU utils-Bibliothek (
GNU libiberty ).
Das Wesentliche der Funktion ist, dass das Programm beendet wird, wenn die Speicherzuordnung fehlschlägt. Eine Implementierung dieser Funktion könnte folgendermaßen aussehen:
void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; }
Dementsprechend können Sie durch Aufrufen der Funktion
xmalloc überall anstelle von
malloc sicher sein, dass das Programm aufgrund der Verwendung des Nullzeigers kein undefiniertes Verhalten aufweist.
Leider ist
xmalloc auch nicht bei allen Krankheiten ein Allheilmittel. Es muss beachtet werden, dass die Verwendung von
xmalloc beim Schreiben von Bibliothekscode nicht
akzeptabel ist . Ich werde etwas später darüber sprechen.
3. Die meisten Kommentare lauteten wie folgt: "In der Praxis gibt malloc niemals NULL zurück ."Zum Glück verstehe ich nicht allein, dass dies der falsche Ansatz ist. Dieser
Kommentar in meiner Unterstützung hat mir sehr gut gefallen:
Aus der Erfahrung, ein solches Thema zu diskutieren, hat man das Gefühl, dass es im Internet zwei Sekten gibt. Anhänger der ersten sind fest davon überzeugt, dass malloc unter Linux niemals NULL zurückgibt. Anhänger des zweiten sind fest davon überzeugt, dass Sie nur fallen müssen, wenn der Speicher im Programm nicht zugewiesen werden konnte, aber im Prinzip nichts getan werden kann. Sie können in keiner Weise überzeugt werden. Besonders wenn sich diese beiden Sekten kreuzen. Sie können es nur als selbstverständlich betrachten. Dabei spielt es keine Rolle, auf welcher Profilressource die Diskussion stattfindet.Ich dachte und beschloss, dem Rat zu folgen und ich werde nicht versuchen zu überzeugen :). Hoffen wir, dass diese Entwicklungsteams nur unkritische Programme schreiben. Wenn sich beispielsweise einige Daten im Spiel verschlechtern oder das Spiel abstürzt, ist dies keine große Sache.
Wichtig ist nur, dass Entwickler von Bibliotheken, Datenbanken usw. dies nicht tun.
Ein Anruf bei Bibliotheks- und verantwortlichen Codeentwicklern
Wenn Sie eine Bibliothek oder einen anderen verantwortlichen Code entwickeln, überprüfen Sie immer den Wert des von der Funktion
malloc / realloc zurückgegebenen Zeigers und geben Sie den Fehlercode nach außen zurück, wenn der Speicher nicht zugewiesen werden konnte.
In Bibliotheken können Sie die
Exit- Funktion nicht aufrufen, wenn Sie keinen Speicher zuordnen konnten. Aus dem gleichen Grund können Sie
xmalloc nicht verwenden. Für viele Anwendungen ist es nicht akzeptabel, sie einfach zu nehmen und zum Absturz zu bringen. Aus diesem Grund kann beispielsweise die Datenbank beschädigt sein. Daten, die viele Stunden lang gezählt wurden, können verloren gehen. Aus diesem Grund ist das Programm möglicherweise anfällig für Denial-of-Service-Schwachstellen, wenn die Multithread-Anwendung nicht ordnungsgemäß mit der wachsenden Last umgeht, sondern einfach beendet wird.
Es kann nicht davon ausgegangen werden, wie und in welchen Projekten die Bibliothek verwendet wird. Daher sollte davon ausgegangen werden, dass die Anwendung sehr wichtige Aufgaben lösen kann. Und es ist nicht gut, ihn nur zu töten, indem man den
Ausgang ruft. Höchstwahrscheinlich wird ein solches Programm unter Berücksichtigung der Möglichkeit geschrieben, dass der Speicher knapp wird, und kann in diesem Fall etwas tun. Beispielsweise kann ein CAD-System aufgrund der starken Speicherfragmentierung keinen ausreichenden Speicherpuffer für die nächste Operation zuweisen. Dies ist jedoch kein Grund für sie, im Notfallmodus mit Datenverlust zu enden. Ein Programm kann es ermöglichen, ein Projekt zu speichern und sich im normalen Modus neu zu starten.
In keinem Fall können Sie sich darauf verlassen, dass
malloc immer Speicher zuweisen kann. Es ist nicht bekannt, auf welcher Plattform und wie die Bibliothek verwendet wird. Wenn auf einer Plattform ein Mangel an Speicher exotisch ist, kann dies auf einer anderen eine sehr häufige Situation sein.
Es ist nicht zu hoffen, dass das Programm abstürzt, wenn
malloc NULL zurückgibt. Alles kann passieren. Wie ich im
Artikel beschrieben habe , kann ein Programm überhaupt Daten mit einer Adresse ungleich Null schreiben. Infolgedessen können einige Daten beschädigt werden, was zu unvorhersehbaren Konsequenzen führt. Sogar
Memset ist gefährlich. Wenn die Daten in umgekehrter Reihenfolge ausgefüllt werden, werden zunächst einige Daten beschädigt, und erst dann stürzt das Programm ab. Aber der Herbst kann zu spät kommen. Wenn zum Zeitpunkt der
Memset- Funktion beschädigte Daten in parallelen Threads
verwendet werden, können die Folgen schwerwiegend sein. Sie können eine beschädigte Transaktion in der Datenbank abrufen oder einen Befehl zum Löschen "unnötiger" Dateien senden. Alles kann rechtzeitig passieren. Ich schlage dem Leser vor, sich unabhängig vorzustellen, wozu die Verwendung von Müll im Speicher führen kann.
Somit hat die Bibliothek nur eine richtige Option für die Arbeit mit
Malloc- Funktionen. Sie müssen SOFORT überprüfen, ob die Funktion zurückgegeben wurde. Wenn sie NULL ist, geben Sie den Fehlerstatus zurück.
Sitelinks
- OOM-Handhabung .
- Spaß mit NULL-Zeigern: Teil 1 , Teil 2 .
- Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte: Teil 1 , Teil 2 , Teil 3 .

Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Andrey Karpov.
Sechster Chromcheck, Nachwort .