Initialisieren Sie immer Ihre Variablen
Der Unterschied zwischen den betrachteten Situationen besteht darin, dass der Fall ohne Initialisierung zu undefiniertem Verhalten führt , während der Fall, in dem Sie sich die Zeit für die Initialisierung genommen haben, einen genau definierten und deterministischen Fehler verursacht. Ich kann nicht betonen, wie sehr diese beiden Fälle voneinander abweichen.
Stellen Sie sich ein hypothetisches Beispiel vor, das einem hypothetischen Mitarbeiter in einem hypothetischen Simulationsprogramm passiert sein könnte. Dieses hypothetische Team versuchte hypothetisch, eine deterministische Simulation durchzuführen, um zu demonstrieren, dass das Produkt, das sie hypothetisch verkauften, den Anforderungen entsprach.
Okay, ich werde mit dem Wort Spritzen aufhören. Ich denke du verstehst den Punkt ;-)
In dieser Simulation gab es Hunderte von nicht initialisierten Variablen. Ein Entwickler hat valgrind für die Simulation ausgeführt und festgestellt, dass mehrere Fehler beim Verzweigen auf nicht initialisierten Wert aufgetreten sind. "Hmm, das sieht so aus, als könnte es zu Nicht-Determinismus kommen, was es schwierig macht, Testläufe zu wiederholen, wenn wir es am dringendsten brauchen." Der Entwickler wandte sich an das Management, aber das Management hatte einen sehr engen Zeitplan und konnte keine Ressourcen sparen, um dieses Problem zu beheben. "Am Ende initialisieren wir alle unsere Variablen, bevor wir sie verwenden. Wir haben gute Codierungspraktiken."
Ein paar Monate vor der endgültigen Auslieferung, wenn sich die Simulation im Churn-Modus befindet und das gesamte Team sprintet, um alle versprochenen Aufgaben des Managements mit einem Budget zu erledigen, das wie jedes jemals finanzierte Projekt zu klein war. Jemand bemerkte, dass sie ein wesentliches Merkmal nicht testen konnten, weil sich der deterministische Sim aus irgendeinem Grund nicht deterministisch zum Debuggen verhielt.
Möglicherweise wurde das gesamte Team angehalten und verbrachte den größten Teil von 2 Monaten damit, die gesamte Codebasis der Simulation zu kämmen, um nicht initialisierte Wertefehler zu beheben, anstatt Funktionen zu implementieren und zu testen. Unnötig zu erwähnen, dass der Mitarbeiter das "Ich habe es Ihnen gesagt" übersprungen hat und sofort anderen Entwicklern geholfen hat, zu verstehen, was nicht initialisierte Werte sind. Seltsamerweise wurden die Codierungsstandards kurz nach diesem Vorfall geändert, um die Entwickler zu ermutigen, ihre Variablen immer zu initialisieren.
Und das ist der Warnschuss. Dies ist die Kugel, die über Ihre Nase streifte. Das eigentliche Problem ist weit, weit, weit, weit heimtückischer, als Sie sich vorstellen.
Die Verwendung eines nicht initialisierten Werts ist "undefiniertes Verhalten" (mit Ausnahme einiger Eckfälle wie char
). Undefiniertes Verhalten (oder kurz UB) ist so wahnsinnig schlecht für Sie, dass Sie niemals glauben sollten, es sei besser als die Alternative. Manchmal können Sie feststellen, dass Ihr bestimmter Compiler das UB definiert und dann sicher verwendet werden kann. Andernfalls ist undefiniertes Verhalten "ein Verhalten, wie es sich der Compiler anfühlt". Es kann etwas bewirken, das Sie als "normal" bezeichnen, wie einen nicht angegebenen Wert. Es kann ungültige Opcodes ausgeben, die möglicherweise dazu führen, dass Ihr Programm sich selbst beschädigt. Möglicherweise wird beim Kompilieren eine Warnung ausgelöst, oder der Compiler betrachtet sie sogar als Fehler.
Oder es kann überhaupt nichts tun
Mein Kanarienvogel in der Kohlenmine für UB ist ein Fall von einer SQL-Engine, über die ich gelesen habe. Verzeihen Sie, dass ich den Artikel nicht gefunden habe. Es gab ein Pufferüberlaufproblem in der SQL-Engine, als Sie einer Funktion eine größere Puffergröße übergeben haben, jedoch nur in einer bestimmten Debian-Version. Der Fehler wurde pflichtgemäß protokolliert und untersucht. Der lustige Teil war: Der Pufferüberlauf wurde überprüft . Es gab Code, um den Pufferüberlauf an Ort und Stelle zu behandeln. Es sah ungefähr so aus:
// move the pointers properly to copy data into a ring buffer.
char* putIntoRingBuffer(char* begin, char* end, char* get, char*put, char* newData, unsigned int dataLength)
{
// If dataLength is very large, we might overflow the pointer
// arithmetic, and end up with some very small pointer number,
// causing us to fail to realize we were trying to write past the
// end. Check this before we continue
if (put + dataLength < put)
{
RaiseError("Buffer overflow risk detected");
return 0;
}
...
// typical ring-buffer pointer manipulation followed...
}
Ich habe meiner Wiedergabe weitere Kommentare hinzugefügt, aber die Idee ist dieselbe. Wenn ein put + dataLength
Zeilenumbruch erfolgt, ist er kleiner als der put
Zeiger (bei ihnen wurde die Kompilierungszeit überprüft, um sicherzustellen, dass das vorzeichenlose int für Neugierige die Größe eines Zeigers hatte). In diesem Fall wissen wir, dass die Standard-Ringpuffer-Algorithmen durch diesen Überlauf verwirrt werden können, und geben daher 0 zurück. Oder doch?
Wie sich herausstellt, ist der Überlauf auf Zeigern in C ++ undefiniert. Da die meisten Compiler Zeiger als Ganzzahlen behandeln, ergibt sich ein typisches Überlaufverhalten von Ganzzahlen, das genau das ist, was wir wollen. Dies ist jedoch undefiniertes Verhalten, dh der Compiler kann alles tun, was er will.
Im Falle dieses Fehlers entschied sich Debian zufällig für die Verwendung einer neuen Version von gcc, auf die keine der anderen wichtigen Linux-Varianten in ihren Produktionsversionen aktualisiert worden war. Diese neue Version von gcc hatte einen aggressiveren Dead-Code-Optimierer. Der Compiler erkannte das undefinierte Verhalten und entschied, dass das Ergebnis der if
Anweisung "Was auch immer die Codeoptimierung am besten macht" ist, was eine absolut legale Übersetzung von UB ist. Dementsprechend wurde die Annahme getroffen, dass die Anweisung niemals die Pufferüberlaufprüfung auslösen und optimieren würde , da ptr+dataLength
sie ptr
ohne einen UB-Zeigerüberlauf if
niemals niedriger sein kann .
Die Verwendung von "sane" UB verursachte tatsächlich bei einem großen SQL-Produkt einen Buffer Overrun-Exploit , bei dem Code geschrieben wurde, um dies zu vermeiden!
Verlassen Sie sich niemals auf undefiniertes Verhalten. Je.
bytes_read
nicht geändert wird (also Null bleibt), warum soll dies ein Fehler sein? Das Programm könnte immer noch auf vernünftige Weise fortgesetzt werden, solange es nicht implizitbytes_read!=0
danach erwartet . Es ist also in Ordnung, dass Desinfektionsmittel sich nicht beschweren. Auf der anderen Seite, wennbytes_read
vorher nicht initialisiert wird, wird das Programm nicht in der Lage sein , in einer vernünftigen Art und Weise fortzusetzen, die Initialisierung so nichtbytes_read
wirklich stellt einen Fehler, der vorher nicht da war.