Compiler erkennt offensichtlich nicht initialisierte Variable nicht
Lesezeit: 9 Minuten
Jabberwocky
Alle C-Compiler, die ich ausprobiert habe, erkennen im folgenden Code-Snippet keine nicht initialisierten Variablen. Doch der Fall liegt hier auf der Hand.
Kümmern Sie sich nicht um die Funktionalität dieses Snippets. Es ist kein echter Code, und ich habe ihn für die Untersuchung dieses Problems entfernt.
BOOL NearEqual (int tauxprecis, int max, int value)
{
int tauxtrouve; // Not initialized at this point
int totaldiff; // Not initialized at this point
for (int i = 0; i < max; i++)
{
if (2 < totaldiff) // At this point totaldiff is not initialized
{
totaldiff = 2;
tauxtrouve = value; // Commenting this line out will produce warning
}
}
return tauxtrouve == tauxprecis ; // At this point tauxtrouve is potentially
// not initialized.
}
Andererseits, wenn ich auskommentiere tauxtrouve = value ;Ich bekomme die "local variable 'tauxtrouve' used without having been initialized" Warnung.
Microsoft Visual C++ 2013 mit allen aktivierten Warnungen
Ich habe keine Ahnung, aber vielleicht ist es die Compiler-Optimierung? Ich bin auch gespannt. Hoffe wir bekommen bald die Antwort.
– Sourav Ghosh
21. November 2014 um 14:35 Uhr
Was passiert, wenn Sie die hinzufügen -pedantic gcc kennzeichnen?
– avgvstvs
21. November 2014 um 14:35 Uhr
Ich bin nicht damit vertraut, wie die Initialisierung getestet wird, aber check out gcc.gnu.org/wiki/Better_Uninitialized_Warnings die ich beim googeln gefunden habe. Besonders CCP assumes a value for uninitialized variables. Wenn ich mir meiner sicher wäre, hätte ich diesen Kommentar abgegeben und geantwortet.
– Peter M
21. November 2014 um 14:42 Uhr
Visual Studio 2013 sagt mir „Fehler C4700: nicht initialisierte lokale Variable ‚totaldiff‘ verwendet“.
– barak manos
21. November 2014 um 15:07 Uhr
stackoverflow.com/q/25151508/541686
– Benutzer541686
21. November 2014 um 19:08 Uhr
Brian Kain
Die Offensichtlichkeit, mit der diese Variable nicht initialisiert wird, ist übertrieben. Die Pfadanalyse kostet Zeit und Ihre Compiler-Anbieter wollten das Feature entweder nicht implementieren oder dachten, es würde Sie zu viel Zeit kosten – oder Sie haben sich einfach nicht explizit dafür entschieden.
Zum Beispiel mit clang:
$ clang -Wall -Wextra -c obvious.c
$ clang -Wall -Wextra --analyze -c obvious.c
obvious.c:9:11: warning: The right operand of '<' is a garbage value
if (2 < totaldiff) // at this point totaldiff is not initialized
^ ~~~~~~~~~
obvious.c:16:21: warning: The left operand of '==' is a garbage value
return tauxtrouve == tauxprecis ; // at this point tauxtrouve is potentially
~~~~~~~~~~ ^
2 warnings generated.
Der Unterschied in der Ausführungszeit für diese naiven Beispiele ist vernachlässigbar. Aber stellen Sie sich eine Übersetzungseinheit mit Tausenden von Zeilen, Dutzenden von Funktionen vor, jede mit Schleifen und starker Verschachtelung. Die Anzahl der Pfade summiert sich schnell und wird zu einer großen Bürde, um zu analysieren, ob die erste Iteration durch die Schleife vor diesem Vergleich erfolgt, ob die Zuweisung erfolgt oder nicht.
BEARBEITEN: @Matthieu weist darauf hin, dass bei LLVM/clang die Pfadanalyse, die erforderlich ist, um die Verwendung des nicht initialisierten Werts zu finden, nicht zusammengesetzt wird, wenn die Verschachtelung aufgrund der vom IR verwendeten SSA-Notation zunimmt.
Es war nicht so einfach wie “-S -emit-llvm” wie ich gehofft hatte, aber ich fand die von ihm beschriebene Ausgabe in SSA-Notation. Ich bin ehrlich, ich bin mit LLVM IR nicht vertraut genug, um sicher zu sein, aber ich nehme Matthieus Wort dafür.
Fazit: verwenden clang mit --analyzeoder überzeugen Sie jemanden, das Problem zu beheben gcc Insekt.
Hast du Zahlen zum Abbau? Zu Ihrer Information, LLVM IR basiert auf der SSA-Notation, die, wie der Name schon sagt, nur eine einzige Zuweisung zu einer einzelnen Variablen erlaubt (oftmals wird der Variablenname im Quellcode mit einem angehängten Index wiederverwendet). Als Ergebnis sind die Pfade Notwendig materialisiert, und die Überprüfung, ob eine Variable möglicherweise nicht initialisiert verwendet wird, ist daher trivial: Für jede Variable wird bei der Berechnung ihrer Zuweisung überprüft, ob sie definitiv initialisiert, möglicherweise nicht initialisiert oder definitiv nicht initialisiert ist. Bei Gebrauch entsprechend warnen.
– Matthias M.
21. November 2014 um 17:42 Uhr
@Matthieu Oh eigentlich bei weitem nicht so trivial wie man meinen könnte. [See here](gist.github.com/voo42/293d3cafa820f8c86e54 für ein einfaches Beispiel, wo Ihr Vorschlag falsche Warnungen ausgeben würde. Sicher machbar, aber nur die Verwendung des SSA-Formulars reicht nicht aus. Ich bin mir eigentlich nicht sicher, ob es eine allgemeine Lösung gibt, bei der nicht alle möglichen Pfade durch eine Funktion überprüft werden.
– Voo
22. November 2014 um 22:54 Uhr
@Voo es gibt keinen, bei dem man nicht auf HP trifft. In Betracht ziehen if (foo(0)) { i = 0 } if (bar(0)) { j = i }. Aber das erfordert das bar(0) => foo(0) Also müssen wir die Werte kennen bar(0) und foo(0) wertet zu – was aufgrund von HP nicht möglich ist, ohne tatsächlich ein Programm auszuführen (und zu hoffen, dass es beendet wird).
– Maciej Piechotka
23. November 2014 um 0:00 Uhr
@Voo: Oh, ich behaupte ÜBERHAUPT nicht, dass eine 100% genaue Vorhersage möglich ist. Deshalb habe ich 3 besagt: das “potentiell nicht initialisiert” ist exakt für den Zustand den du beschreibst. Und deshalb hat Clang 2 Warnungen: -Wuninitialized (standardmäßig aktiviert) gibt nur Warnungen für Variablen aus, die währenddessen definitiv nicht initialisiert sind -Wmaybe-uninitialized (standardmäßig nicht aktiviert) gibt nur Warnungen für Variablen aus, die möglicherweise nicht initialisiert sind. Also ja, letzteres könnte falsche Warnungen für verworrenen Code geben; Ich würde argumentieren, dass Sie den Code besser reparieren, aber Sie können ihn ignorieren.
– Matthias M.
23. November 2014 um 11:34 Uhr
Das IR ist eigentlich einfach, wenn man das Rauschen ignoriert 🙂 Zum Beispiel gibt es hier 2 Auftritte von tauxtrouve: tauxtrouve.0 und tauxtrouve.1. Das phi Magic wählt den Wert abhängig vom Vorgänger: phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ] ergibt einen Wert vom Typ i32wenn der Vorgänger das benannte Label war 5 dann gibt es nach %value und wenn es der genannte war 3 es ergibt tauxtrouve.0; undef ist ein spezieller Wert, der das Fehlen einer Initialisierung darstellt, und br ist eine Bedingung goto…
– Matthias M.
23. November 2014 um 11:41 Uhr
hackt
Ja, es sollte eine Warnung über diese nicht initialisierte Variable auslösen, aber es ist so ein GCC-Bug. Das dort angegebene Beispiel lautet:
unsigned bmp_iter_set ();
int something (void);
void bitmap_print_value_set (void)
{
unsigned first;
for (; bmp_iter_set (); )
{
if (!first)
something ();
first = 0;
}
}
Und diagnostiziert -O2 -W -Wall.
Leider ist dieses Jahr das 10-jährige Jubiläum dieses Fehlers!
Meine Güte.. ein zehn Jahre alter Bug, mit einer Kommentarliste, die sich wie die Verkörperung der schlimmsten Befürchtungen liest snark-iness in einem OSS-Projekt
– Peter M
21. November 2014 um 14:49 Uhr
@PeterM; Leider ist dieser 10 Jahre alte Fehler noch nicht behoben!
– Hacken
21. November 2014 um 14:51 Uhr
#WontFix #NotSexyEnoughToCarryabout
– Peter M
21. November 2014 um 14:53 Uhr
@Lundin Sie schreiben in Ihrer Antwort, dass Sie vom Compiler “nicht erwarten können”, Fehler zu erkennen, und dass “der Compiler nur zwei Aufgaben hat”. Tatsächlich war das vor 20 Jahren richtig, heute nicht mehr so richtig. Compiler haben sich enorm weiterentwickelt, und heutzutage verlassen sich die Leute auf Compiler, um offensichtliche Fehler zu erkennen. Trotz dieses undefinierten Verhaltens wäre es schön (und erwartet), dass der Compiler eine Warnung generiert. Nicht um UB zu definieren, sondern um den Programmierer wissen zu lassen, dass er es falsch macht. Dies kann nicht sein technisch Fehler, aber es ist definitiv ein Benutzererfahrungsfehler.
– Das paramagnetische Croissant
21. November 2014 um 15:12 Uhr
Ich sehe mich in der Zukunft lesen: “..this bug was not behoben due to historische Gründe.“
– JohnTortugo
26. November 2014 um 13:17 Uhr
Nate Eldredge
Diese Antwort richtet sich nur an GCC.
Nach weiteren Nachforschungen und Kommentaren ist mehr los als in meiner vorherigen Antwort. Dieses Code-Snippet enthält zwei nicht initialisierte Variablen, von denen jede aus einem anderen Grund nicht erkannt wird.
Zunächst einmal die GCC-Dokumentation für die -Wuninitialized Möglichkeit sagt:
Da diese Warnungen von der Optimierung abhängen, hängen die genauen Variablen oder Elemente, für die es Warnungen gibt, von den genauen Optimierungsoptionen und der verwendeten GCC-Version ab.
Frühere Versionen des GCC-Handbuchs haben dies expliziter formuliert. Hier ein Auszug aus der Handbuch für GCC 3.3.6:
Diese Warnungen sind nur bei der Optimierung der Kompilierung möglich, da sie Datenflussinformationen erfordern, die nur bei der Optimierung berechnet werden. Wenn Sie -O nicht angeben, erhalten Sie diese Warnungen einfach nicht.
Es scheint, dass die aktuelle Version ohne nicht initialisierte Variablen einige Warnungen ausgeben kann -Oaber Sie erzielen damit immer noch viel bessere Ergebnisse.
Wenn ich Ihr Beispiel mit kompiliere gcc -std=c99 -Wall -OIch bekomme:
foo.c: In function ‘NearEqual’:
foo.c:15:21: warning: ‘tauxtrouve’ is used uninitialized in this function [-Wuninitialized]
return tauxtrouve == tauxprecis ; // at this point tauxtrouve is potentially
^
(Beachten Sie, dass dies mit GCC 4.8.2 gilt, da ich 4.9.x nicht installiert habe, aber das Prinzip sollte dasselbe sein.)
Das erkennt also die Tatsache, dass tauxtrouve ist nicht initialisiert.
Wenn wir den Code jedoch teilweise reparieren, indem wir einen Initialisierer für hinzufügen tauxtrouve (aber nicht für totaldiff), dann gcc -std=c99 -Wall -O akzeptiert es ohne Vorwarnung. Dies scheint ein Beispiel für den in der Antwort von Hackks zitierten “Fehler” zu sein.
Es stellt sich die Frage, ob dies wirklich als Fehler angesehen werden sollte: GCC verspricht nicht, jede mögliche Instanz einer nicht initialisierten Variablen abzufangen. In der Tat kann es dies nicht mit perfekter Genauigkeit tun, denn das ist die Halteproblem. Warnungen wie diese können also hilfreich sein, wenn sie funktionieren, aber das Fehlen von Warnungen beweist nicht, dass Ihr Code frei von nicht initialisierten Variablen ist! Sie sind wirklich kein Ersatz für die sorgfältige Überprüfung des eigenen Codes.
In dem durch Hacks verlinkter Fehlerberichtgibt es viele Diskussionen darüber, ob der Fehler überhaupt behebbar ist oder ob der Versuch, dieses bestimmte Konstrukt zu erkennen, zu einer inakzeptablen Falsch-Positiv-Rate für anderen korrekten Code führen würde.
Ich glaube, das OP ist mehr besorgt über die Erkennung totaldiff nicht initialisiert.
– Peter M
21. November 2014 um 19:12 Uhr
@PeterM: Guter Punkt. Es gibt hier eigentlich zwei getrennte Probleme, und ich habe meine Antwort umgeschrieben, um beide anzusprechen.
– Nate Eldredge
22. November 2014 um 18:42 Uhr
Michael, ich weiß nicht, auf welcher Version von Visual Studio 2013 Sie das ausprobiert haben, aber es ist mit Sicherheit veraltet. Visual Studio 2013-Update 4 korrekt erzeugt die folgende Fehlermeldung bei der ersten Verwendung von totaldiff:
error C4700: uninitialized local variable 'totaldiff' used
Sie sollten erwägen, Ihre Arbeitsumgebung zu aktualisieren.
Übrigens, hier ist, was ich direkt im Editor sehe:
14126700cookie-checkCompiler erkennt offensichtlich nicht initialisierte Variable nichtyes
Ich habe keine Ahnung, aber vielleicht ist es die Compiler-Optimierung? Ich bin auch gespannt. Hoffe wir bekommen bald die Antwort.
– Sourav Ghosh
21. November 2014 um 14:35 Uhr
Was passiert, wenn Sie die hinzufügen
-pedantic
gcc kennzeichnen?– avgvstvs
21. November 2014 um 14:35 Uhr
Ich bin nicht damit vertraut, wie die Initialisierung getestet wird, aber check out gcc.gnu.org/wiki/Better_Uninitialized_Warnings die ich beim googeln gefunden habe. Besonders
CCP assumes a value for uninitialized variables
. Wenn ich mir meiner sicher wäre, hätte ich diesen Kommentar abgegeben und geantwortet.– Peter M
21. November 2014 um 14:42 Uhr
Visual Studio 2013 sagt mir „Fehler C4700: nicht initialisierte lokale Variable ‚totaldiff‘ verwendet“.
– barak manos
21. November 2014 um 15:07 Uhr
stackoverflow.com/q/25151508/541686
– Benutzer541686
21. November 2014 um 19:08 Uhr