Compiler erkennt offensichtlich nicht initialisierte Variable nicht

Lesezeit: 9 Minuten

Benutzeravatar von Jabberwocky
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.

Ich habe diese Compiler ausprobiert:

  • GCC 4.9.2 mit -Wall -WExtra
  • 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

Benutzeravatar von Brian Cain
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.

; Function Attrs: nounwind uwtable
define i32 @NearEqual(i32 %tauxprecis, i32 %max, i32 %value) #0 {
  br label %1

; <label>:1                                       ; preds = %7, %0
  %tauxtrouve.0 = phi i32 [ undef, %0 ], [ %tauxtrouve.1, %7 ]
  %i.0 = phi i32 [ 0, %0 ], [ %8, %7 ]
  %2 = icmp slt i32 %i.0, %max
  br i1 %2, label %3, label %9

; <label>:3                                       ; preds = %1
  %4 = icmp slt i32 2, 2
  br i1 %4, label %5, label %6

; <label>:5                                       ; preds = %3
  br label %6

; <label>:6                                       ; preds = %5, %3
  %tauxtrouve.1 = phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ]
  br label %7

; <label>:7                                       ; preds = %6
  %8 = add nsw i32 %i.0, 1
  br label %1

; <label>:9                                       ; preds = %1
  %10 = icmp eq i32 %tauxtrouve.0, %tauxprecis
  %11 = zext i1 %10 to i32
  ret i32 %11
}

  • 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

Benutzeravatar von hackks
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

Benutzeravatar von Nate Eldredge
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:

Visual Studio 2013 hat den Fehler abgefangen

1412670cookie-checkCompiler erkennt offensichtlich nicht initialisierte Variable nicht

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy