Die C ++ Core Guidelines haben die Regel ES.20: Ein Objekt immer initialisieren .
Vermeiden Sie zuvor festgelegte Fehler und das damit verbundene undefinierte Verhalten. Vermeiden Sie Probleme mit dem Verständnis komplexer Initialisierungen. Vereinfachen Sie das Refactoring.
Aber diese Regel hilft nicht, Fehler zu finden, sondern verbirgt sie nur.
Angenommen, ein Programm hat einen Ausführungspfad, in dem es eine nicht initialisierte Variable verwendet. Es ist ein Fehler. Abgesehen von undefiniertem Verhalten bedeutet dies auch, dass ein Fehler aufgetreten ist und das Programm möglicherweise nicht die Produktanforderungen erfüllt. Wenn es in der Produktion eingesetzt wird, kann es zu einem Geldverlust oder noch schlimmer kommen.
Wie überprüfen wir Fehler? Wir schreiben Tests. Tests decken jedoch nicht 100% der Ausführungspfade ab, und Tests decken niemals 100% der Programmeingaben ab. Darüber hinaus deckt sogar ein Test einen fehlerhaften Ausführungspfad ab - er kann immer noch bestehen. Es ist undefiniertes Verhalten, eine nicht initialisierte Variable kann einen etwas gültigen Wert haben.
Aber zusätzlich zu unseren Tests haben wir die Compiler, die so etwas wie 0xCDCDCDCD in nicht initialisierte Variablen schreiben können. Dies verbessert die Erkennungsrate der Tests geringfügig.
Noch besser - es gibt Tools wie Address Sanitizer, die alle Lesevorgänge von nicht initialisierten Speicherbytes erfassen.
Und schließlich gibt es statische Analysatoren, die das Programm anzeigen und feststellen können, dass sich auf diesem Ausführungspfad ein Read-Before-Set befindet.
Wir haben also viele leistungsfähige Werkzeuge, aber wenn wir die Variable initialisieren, finden Desinfektionsmittel nichts .
int bytes_read = 0;
my_read(buffer, &bytes_read); // err_t my_read(buffer_t, int*);
// bytes_read is not changed on read error.
// It's a bug of "my_read", but detection is suppressed by initialization.
buffer.shrink(bytes_read); // Uninitialized bytes_read could be detected here.
// Another bug: use empty buffer after read error.
use(buffer);
Es gibt eine andere Regel: Wenn die Programmausführung auf einen Fehler stößt, sollte das Programm so schnell wie möglich abstürzen. Sie müssen es nicht am Leben halten, nur abstürzen, ein Absturzabbild schreiben und es den Ingenieuren zur Untersuchung geben.
Das Initialisieren von Variablen bewirkt unnötigerweise das Gegenteil - das Programm wird am Leben gehalten, wenn es sonst bereits einen Segmentierungsfehler bekommen würde.
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.\0
. Wenn dokumentiert ist, dass dies nicht der Fall ist, ist Ihr Anrufcode fehlerhaft. Wenn Sie Ihren Anrufcode korrigieren, um ihnbytes_read==0
vor dem Anruf zu überprüfen , kehren Sie zu Ihrem Ausgangspunkt zurück: Ihr Code ist fehlerhaft, wenn Sie ihn nicht initialisierenbytes_read
, und in diesem Fall sicher. ( Normalerweise sollen Funktionen ihre Out-Parameter auch im Fehlerfall ausfüllen : nicht wirklich. Sehr oft werden die Ausgänge entweder alleine oder undefiniert gelassen.)err_t
zurückgegebene von ignoriertmy_read()
? Wenn es irgendwo im Beispiel einen Fehler gibt, ist es das.Antworten:
Ihre Argumentation ist in mehreren Punkten falsch:
bytes_read
den Wert hat,10
als dass er den Wert hat0xcdcdcdcd
.Die Idee hinter der Anleitung, Variablen immer zu initialisieren, besteht darin, diese beiden Situationen zu ermöglichen
Die Variable enthält von Anfang an einen nützlichen Wert. Wenn Sie dies mit der Anweisung kombinieren, eine Variable nur dann zu deklarieren, wenn Sie sie benötigen, können Sie verhindern, dass zukünftige Wartungsprogrammierer in die Falle geraten, eine Variable zwischen ihrer Deklaration und der ersten Zuweisung zu verwenden, bei der die Variable vorhanden, aber nicht initialisiert wäre.
Die Variable enthält einen definierten Wert, den Sie später testen können, um festzustellen, ob eine Funktion wie
my_read
die den Wert aktualisiert hat. Ohne Initialisierung können Sie nicht feststellen, obbytes_read
tatsächlich ein gültiger Wert vorliegt, da Sie nicht wissen, mit welchem Wert er begonnen hat.quelle
= 0;
. Der Zweck des Hinweises besteht darin, die Variable an der Stelle zu deklarieren, an der Sie einen nützlichen Wert dafür haben, und diesen Wert sofort zuzuweisen. Dies wird in den unmittelbar folgenden Regeln ES21 und ES22 explizit verdeutlicht. Diese drei sollten alle als zusammenarbeitend verstanden werden; nicht als einzelne unabhängige Regeln.Sie haben geschrieben, "diese Regel hilft nicht, Fehler zu finden, sie versteckt sie nur" - nun, das Ziel der Regel ist nicht, Fehler zu finden, sondern sie zu vermeiden . Und wenn ein Fehler vermieden wird, ist nichts verborgen.
Lassen Sie uns das Problem anhand Ihres Beispiels erläutern: Nehmen wir an, die
my_read
Funktion verfügtbytes_read
unter allen Umständen über den schriftlichen Initialisierungsvertrag , tut dies jedoch nicht im Fehlerfall, so dass sie zumindest für diesen Fall fehlerhaft ist. Sie möchten diesen Fehler in der Laufzeitumgebung anzeigen, indem Sie denbytes_read
Parameter nicht zuerst initialisieren . Solange Sie sicher wissen, dass ein Adressbereinigungsprogramm vorhanden ist, ist dies in der Tat eine Möglichkeit, einen solchen Fehler zu erkennen. Um den Fehler zu beheben, muss diemy_read
Funktion intern geändert werden.Es gibt aber eine andere Sichtweise, die mindestens gleichermaßen gilt: Das fehlerhafte Verhalten ergibt sich nur aus der Kombination von nicht
bytes_read
vorher initialisieren undmy_read
danach aufrufen (wobei die Erwartung danachbytes_read
initialisiert wird). Dies ist eine Situation, die in Komponenten der realen Welt häufig vorkommt, wenn die geschriebene Spezifikation für eine Funktion wiemy_read
nicht 100% klar oder sogar falsch ist, was das Verhalten im Fehlerfall angeht. Solangebytes_read
das Programm jedoch vor dem Aufruf auf Null initialisiert wird, verhält es sich genauso, als ob die Initialisierung intern durchgeführt worden wäremy_read
, und verhält sich daher korrekt. In dieser Kombination gibt es keinen Fehler im Programm.Daraus folgt meine Empfehlung: Verwenden Sie den nicht initialisierenden Ansatz nur, wenn
Dies sind Bedingungen, die Sie normalerweise in Testcode für eine bestimmte Tooling-Umgebung festlegen können.
Im Produktionscode ist es jedoch besser, eine solche Variable immer im Voraus zu initialisieren. Dies ist der defensivere Ansatz, der Fehler verhindert, wenn der Vertrag unvollständig oder falsch ist oder wenn das Adressbereinigungsprogramm oder ähnliche Sicherheitsmaßnahmen nicht aktiviert sind. Und die "Absturz-früh" -Regel gilt, wie Sie richtig geschrieben haben, wenn die Programmausführung auf einen Fehler stößt. Wenn jedoch eine Variable im Voraus initialisiert wird, bedeutet dies, dass nichts falsch ist, und die weitere Ausführung nicht angehalten werden muss.
quelle
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:
Ich habe meiner Wiedergabe weitere Kommentare hinzugefügt, aber die Idee ist dieselbe. Wenn ein
put + dataLength
Zeilenumbruch erfolgt, ist er kleiner als derput
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 , daptr+dataLength
sieptr
ohne einen UB-Zeigerüberlaufif
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.
quelle
bool
Dies ist ein hervorragendes Beispiel für offensichtliche Probleme, die jedoch an anderer Stelle auftreten, sofern Sie nicht davon ausgehen, dass Sie auf einer sehr hilfreichen Plattform wie x86 oder ARM oder MIPS arbeiten, bei der all diese Probleme zum Opcode-Zeitpunkt behoben werden.switch
aufgrund der Größe der Ganzzahlarithmetik kleiner als 8 ist, damit er schnelle Anweisungen verwenden kann, bei denen davon ausgegangen wird, dass kein "großer" Wert eingeht Ein nicht angegebener Wert (der nach den Regeln des Compilers niemals erstellt werden könnte) wird angezeigt und führt zu einem unerwarteten Ergebnis. Dann springt plötzlich ein großer Sprung vom Ende einer Sprungtabelle. Wenn Sie hier nicht angegebene Ergebnisse zulassen, muss jede switch-Anweisung im Programm über zusätzliche Traps verfügen, um diese Fälle zu unterstützen, die "niemals auftreten" können.Ich arbeite hauptsächlich in einer funktionalen Programmiersprache, in der Sie keine Variablen neu zuweisen dürfen. Je. Damit ist diese Klasse von Fehlern vollständig beseitigt. Dies schien zunächst eine enorme Einschränkung zu sein, zwingt Sie jedoch dazu, Ihren Code so zu strukturieren, dass er mit der Reihenfolge übereinstimmt, in der Sie neue Daten lernen. Dies vereinfacht Ihren Code und erleichtert die Wartung.
Diese Gewohnheiten können auch auf imperative Sprachen übertragen werden. Es ist fast immer möglich, den Code umzugestalten, um zu vermeiden, dass eine Variable mit einem Dummy-Wert initialisiert wird. Das ist es, wozu Sie diese Richtlinien auffordern. Sie möchten, dass Sie etwas Sinnvolles einfügen, nicht etwas, das nur automatisierte Werkzeuge glücklich macht.
Ihr Beispiel mit einer C-API ist etwas kniffliger. In diesen Fällen , wenn ich verwende die Funktion werde ich auf Null initialisieren die Compiler , um zu verhindern beschweren, aber eine Zeit in den
my_read
Unit - Tests, werde ich auf etwas anderes um sicherzustellen , dass der Fehler richtig funktioniert initialisieren. Sie müssen nicht bei jeder Verwendung alle möglichen Fehlerbedingungen testen.quelle
Nein, es versteckt keine Bugs. Stattdessen wird das Verhalten so determiniert, dass ein Entwickler einen Fehler reproduzieren kann, wenn ein Benutzer auf einen Fehler stößt.
quelle
TL; DR: Es gibt zwei Möglichkeiten, dieses Programm zu korrigieren, Ihre Variablen zu initialisieren und zu beten. Nur eine liefert konsistente Ergebnisse.
Bevor ich Ihre Frage beantworten kann, muss ich zunächst erklären, was undefiniertes Verhalten bedeutet. Eigentlich lasse ich einen Compilerautor den Großteil der Arbeit machen:
Wenn Sie diese Artikel nicht lesen möchten, ist ein TL; DR:
Der Archetyp der "Dämonen, die aus der Nase fliegen" hat die Implikationen dieser Tatsache leider überhaupt nicht vermittelt. Es sollte zwar beweisen, dass alles passieren konnte, aber es war so unglaublich, dass es größtenteils abgeschüttelt wurde.
Die Wahrheit ist jedoch, dass Undefiniertes Verhalten die Kompilierung selbst beeinflusst, lange bevor Sie versuchen, das Programm zu verwenden (instrumentiert oder nicht, innerhalb eines Debuggers oder nicht) und sein Verhalten vollständig ändern kann.
Ich finde das Beispiel in Teil 2 oben auffällig:
verwandelt sich in:
denn es ist offensichtlich, dass
P
dies nicht möglich ist,0
da es vor der Überprüfung dereferenziert wird.Wie trifft dies auf Ihr Beispiel zu?
Nun, Sie haben den allgemeinen Fehler gemacht, anzunehmen, dass Undefiniertes Verhalten einen Laufzeitfehler verursachen würde. Es darf nicht.
Stellen wir uns vor, die Definition von
my_read
lautet:und verfahren Sie wie von einem guten Compiler mit Inlining erwartet:
Dann optimieren wir, wie von einem guten Compiler erwartet, nutzlose Zweige:
bytes_read
würde nicht initialisiert verwendet, wennresult
nicht0
result
das niemals sein wird0
!So
result
ist es niemals0
:Oh,
result
wird nie benutzt:Oh, wir können die Erklärung verschieben von
bytes_read
:Und hier ist eine streng bestätigende Transformation des Originals, und kein Debugger fängt eine nicht initialisierte Variable ab, weil es keine gibt.
Ich war auf diesem Weg, das Problem zu verstehen, wenn das erwartete Verhalten und die Montage nicht übereinstimmen, ist wirklich kein Spaß.
quelle
Sehen wir uns Ihren Beispielcode genauer an:
Dies ist ein gutes Beispiel. Wenn wir einen solchen Fehler antizipieren, können wir die Zeile einfügen
assert(bytes_read > 0);
und diesen Fehler zur Laufzeit abfangen, was mit einer nicht initialisierten Variablen nicht möglich ist.Aber nehmen wir an, wir tun es nicht und finden einen Fehler in der Funktion
use(buffer)
. Wir laden das Programm in den Debugger, überprüfen die Rückverfolgung und stellen fest, dass es von diesem Code aufgerufen wurde. Also setzen wir einen Haltepunkt oben in dieses Snippet, führen es erneut aus und reproduzieren den Fehler. Wir versuchen es in einem Schritt zu fangen.Wenn wir nicht initialisiert haben
bytes_read
, enthält es Müll. Es muss nicht jedes Mal den gleichen Müll enthalten. Wir treten an der Linie vorbeimy_read(buffer, &bytes_read);
. Wenn es ein anderer Wert als zuvor ist, können wir unseren Fehler möglicherweise überhaupt nicht reproduzieren! Möglicherweise funktioniert es beim nächsten Mal mit der gleichen Eingabe aus Versehen. Wenn es konstant null ist, erhalten wir ein konsistentes Verhalten.Wir überprüfen den Wert, vielleicht sogar auf einem Backtrace im selben Lauf. Wenn es Null ist, können wir sehen, dass etwas nicht stimmt;
bytes_read
sollte bei Erfolg nicht Null sein. (Oder wenn es sein kann, möchten wir es vielleicht auf -1 initialisieren.) Wir können den Fehler wahrscheinlich hier abfangen. Wennbytes_read
ein plausibler Wert jedoch falsch ist, können wir ihn auf einen Blick erkennen?Dies gilt insbesondere für Zeiger: Ein NULL-Zeiger ist in einem Debugger immer offensichtlich, kann auf einfache Weise getestet werden und sollte auf moderner Hardware fehlerhaft sein, wenn wir versuchen, ihn zu dereferenzieren. Ein Garbage Pointer kann später zu nicht reproduzierbaren Speicherfehlern führen. Ein Debugging dieser Fehler ist nahezu unmöglich.
quelle
Das OP verlässt sich nicht auf undefiniertes Verhalten oder zumindest nicht genau. In der Tat ist es schlecht, sich auf undefiniertes Verhalten zu verlassen. Gleichzeitig ist das Verhalten eines Programms in einem unerwarteten Fall ebenfalls undefiniert, jedoch eine andere Art von undefiniert. Wenn Sie eine Variable auf Null, aber Sie haben nicht die Absicht , einen Ausführungspfad zu haben , dass Anwendungen , daß Erstnullabgleich wird sanely Ihr Programm verhalten , wenn Sie einen Fehler haben und tun haben einen solchen Weg? Du bist jetzt im Unkraut; Sie haben nicht geplant, diesen Wert zu verwenden, aber Sie verwenden ihn trotzdem. Möglicherweise ist es harmlos, oder das Programm stürzt ab, oder das Programm beschädigt unbemerkt Daten. Sie wissen es nicht.
Was das OP sagt ist, dass es Werkzeuge gibt, die Ihnen helfen, diesen Fehler zu finden, wenn Sie sie zulassen. Wenn Sie den Wert nicht initialisieren, ihn aber trotzdem verwenden, gibt es statische und dynamische Analysatoren, die Ihnen mitteilen, dass Sie einen Fehler haben. Ein statischer Analysator informiert Sie, bevor Sie mit dem Testen des Programms beginnen. Wenn Sie andererseits den Wert blind initialisieren, können die Analysatoren nicht erkennen, dass Sie nicht geplant haben, diesen Anfangswert zu verwenden, und Ihr Fehler bleibt somit unentdeckt. Wenn Sie Glück haben, ist es harmlos oder stürzt das Programm nur ab. Wenn Sie Pech haben, werden die Daten unbemerkt beschädigt.
Der einzige Ort, an dem ich mit dem OP nicht einverstanden bin, ist ganz am Ende, wo er sagt, "wenn es sonst schon einen Segmentierungsfehler bekommen würde". Tatsächlich liefert eine nicht initialisierte Variable keinen zuverlässigen Segmentierungsfehler. Stattdessen würde ich sagen, dass Sie statische Analysetools verwenden sollten, mit denen Sie nicht einmal versuchen können, das Programm auszuführen.
quelle
Eine Antwort auf Ihre Frage muss in die verschiedenen Arten von Variablen unterteilt werden, die in einem Programm angezeigt werden:
Lokale Variablen
Normalerweise sollte die Deklaration genau dort sein, wo die Variable zuerst ihren Wert erhält. Deklarieren Sie keine Variablen wie im alten Stil C:
Damit entfallen 99% des Initialisierungsbedarfs, die Variablen haben ihren endgültigen Wert von Anfang an. Die wenigen Ausnahmen bestehen, wenn die Initialisierung von einer bestimmten Bedingung abhängt:
Ich halte es für eine gute Idee, diese Fälle so zu schreiben:
Ich e. Setzen Sie explizit voraus, dass eine sinnvolle Initialisierung Ihrer Variablen durchgeführt wird.
Mitgliedsvariablen
Hier stimme ich dem zu, was die anderen Antwortenden gesagt haben: Diese sollten immer von den Konstruktoren / Initialisierer-Listen initialisiert werden. Andernfalls wird es Ihnen schwer fallen, die Konsistenz zwischen Ihren Mitgliedern zu gewährleisten. Wenn Sie eine Gruppe von Mitgliedern haben, die nicht in allen Fällen initialisiert werden müssen, müssen Sie Ihre Klasse umgestalten und diese Mitglieder zu einer abgeleiteten Klasse hinzufügen, wo sie immer benötigt werden.
Puffer
Hier bin ich mit den anderen Antworten nicht einverstanden. Wenn Leute religiös werden, um Variablen zu initialisieren, initialisieren sie häufig Puffer wie folgt:
Ich halte das fast immer für schädlich: Der einzige Effekt dieser Initialisierungen ist, dass sie Werkzeuge wie
valgrind
machtlos machen. Jeder Code, der mehr aus den initialisierten Puffern liest, als er sollte, ist sehr wahrscheinlich ein Fehler. Mit der Initialisierung kann dieser Fehler jedoch nicht von angezeigt werdenvalgrind
. Verwenden Sie sie daher nur, wenn Sie wirklich darauf vertrauen, dass der Speicher mit Nullen gefüllt ist (und in diesem Fall schreiben Sie einen Kommentar, in dem Sie angeben, wofür Sie die Nullen benötigen).Ich würde auch dringend empfehlen, Ihrem Build-System ein Ziel hinzuzufügen, auf dem die gesamte Testsuite
valgrind
oder ein ähnliches Tool ausgeführt wird, um Fehler und Speicherverluste vor der Initialisierung aufzudecken. Dies ist wertvoller als alle Vorinitialisierungen von Variablen. Diesesvalgrind
Ziel sollte regelmäßig ausgeführt werden, vor allem bevor Code veröffentlicht wird.Globale Variablen
Sie können keine globalen Variablen haben, die nicht initialisiert sind (zumindest in C / C ++ usw.). Stellen Sie daher sicher, dass diese Initialisierung Ihren Wünschen entspricht.
quelle
Base& b = foo() ? new Derived1 : new Derived2;
Base &b = base_factory(which);
. Dies ist am nützlichsten, wenn Sie den Code mehrmals aufrufen müssen oder wenn Sie das Ergebnis zu einer Konstanten machen können.?:
eine PITA ist und eine Factory-Funktion immer noch überfordert ist. Es gibt nur wenige Fälle, aber es gibt sie.Ein anständiger C-, C ++ - oder Objective-C-Compiler mit den richtigen Compileroptionen teilt Ihnen beim Kompilieren mit, ob eine Variable verwendet wird, bevor ihr Wert festgelegt wird. Da die Verwendung des Werts einer nicht initialisierten Variablen in diesen Sprachen ein undefiniertes Verhalten ist, ist das Festlegen eines Werts vor der Verwendung kein Hinweis, keine Richtlinie oder bewährte Methode. ansonsten ist dein programm absolut kaputt. In anderen Sprachen, wie Java und Swift, wird der Compiler Ihnen niemals erlauben, eine Variable zu verwenden, bevor sie initialisiert wird.
Es gibt einen logischen Unterschied zwischen "Initialisieren" und "Wert setzen". Wenn ich den Umrechnungskurs zwischen Dollar und Euro finden möchte, schreibe ich "double rate = 0.0;" Dann ist für die Variable ein Wert festgelegt, der jedoch nicht initialisiert wurde. Die hier gespeicherte 0.0 hat überhaupt nichts mit dem korrekten Ergebnis zu tun. In dieser Situation hat der Compiler keine Chance, Ihnen mitzuteilen, wenn Sie aufgrund eines Fehlers nie die richtige Conversion-Rate speichern. Wenn Sie gerade "double rate" geschrieben haben; und nie eine sinnvolle Conversion-Rate gespeichert, würde der Compiler Ihnen sagen.
Also: Initialisieren Sie keine Variable, nur weil der Compiler angibt, dass sie verwendet wird, ohne initialisiert zu werden. Das verbirgt einen Bug. Das eigentliche Problem ist, dass Sie eine Variable verwenden, die Sie nicht verwenden sollten, oder dass Sie in einem Codepfad keinen Wert festgelegt haben. Beheben Sie das Problem, verstecken Sie es nicht.
Initialisieren Sie keine Variable, nur weil der Compiler Ihnen möglicherweise mitteilt, dass sie verwendet wird, ohne initialisiert zu werden. Wieder verstecken Sie Probleme.
Deklarieren Sie die zu verwendenden Variablen. Dies erhöht die Wahrscheinlichkeit, dass Sie es zum Zeitpunkt der Deklaration mit einem aussagekräftigen Wert initialisieren können.
Vermeiden Sie die Wiederverwendung von Variablen. Wenn Sie eine Variable wiederverwenden, wird sie höchstwahrscheinlich auf einen unbrauchbaren Wert initialisiert, wenn Sie sie für den zweiten Zweck verwenden.
Es wurde angemerkt, dass einige Compiler falsche Negative haben und dass das Überprüfen auf Initialisierung dem Problem des Anhaltens entspricht. Beides ist in der Praxis irrelevant. Wenn ein Compiler zehn Jahre nach der Meldung des Fehlers keine Verwendung einer nicht initialisierten Variablen findet, ist es an der Zeit, nach einem alternativen Compiler zu suchen. Java implementiert dies zweimal. Einmal im Compiler, einmal im Verifier, ohne Probleme. Der einfache Weg, um das Problem des Anhaltens zu umgehen, besteht nicht darin, dass eine Variable vor der Verwendung initialisiert wird, sondern dass sie vor der Verwendung auf eine Weise initialisiert wird, die durch einen einfachen und schnellen Algorithmus überprüft werden kann.
quelle