Ich habe folgenden Code:
#include <iostream>
#include <complex>
using namespace std;
int main() {
complex<int> delta;
complex<int> mc[4] = {0};
for(int di = 0; di < 4; di++, delta = mc[di]) {
cout << di << endl;
}
return 0;
}
Ich erwarte, dass es "0, 1, 2, 3" ausgibt und stoppt, aber es gibt eine endlose Reihe von "0, 1, 2, 3, 4, 5, ....." aus.
Es sieht so aus, als ob der Vergleich di<4
nicht gut funktioniert und immer true zurückgibt.
Wenn ich nur ,delta=mc[di]
auskommentiere, erhalte ich wie gewohnt "0, 1, 2, 3". Was ist das Problem mit der unschuldigen Aufgabe?
Ich verwende Ideone.com g ++ C ++ 14 mit der Option -O2 .
c++
gcc
undefined-behavior
eivour
quelle
quelle
cout << di
ist wahrscheinlich, dass der Stream-Einfügeoperator für Komplex die Adressedi
an einen "undurchsichtigen" Code weitergibt (oder dass der Stream-Einfügeoperator für Komplex selbst undurchsichtig ist - was mich überraschen würde obwohl). Und je nachdem, was dieser "undurchsichtige" Code tut, könnte das Verhalten des Programms immer noch gut definiert sein. Ich sage nicht, dass es in diesem Fall unmöglich wäre, eine Warnung ohne zu viele falsch positive (oder sogar falsch positive) Ergebnisse abzugeben. Nur dass es ziemlich schwer wäre.Antworten:
Dies ist auf undefiniertes Verhalten zurückzuführen. Sie greifen
mc
bei der letzten Iteration Ihrer Schleife außerhalb der Grenzen auf das Array zu . Einige Compiler führen möglicherweise eine aggressive Schleifenoptimierung unter der Annahme durch, dass kein undefiniertes Verhalten vorliegt. Die Logik wäre wie folgt:mc
außerhalb der Grenzen ist ein undefiniertes Verhaltendi < 4
ist immer wahr, da sonstmc[di]
undefiniertes Verhalten aufrufen würdegcc mit aktivierter Optimierung und Verwendung des
-fno-aggressive-loop-optimizations
Flags verschwindet das Verhalten der Endlosschleife ( siehe live ). Während ein Live-Beispiel mit Optimierung, aber ohne -fno-aggressive-Schleifenoptimierungen, das von Ihnen beobachtete Endlosschleifenverhalten zeigt.Ein Godbolt-Live-Beispiel des Codes zeigt, dass die
di < 4
Prüfung entfernt und durch ein bedingungsloses jmp ersetzt wurde:jmp .L6
Dies ist fast identisch mit dem in GCC vor 4.8 Breaks Broken SPEC 2006 Benchmarks beschriebenen Fall . Die Kommentare zu diesem Artikel sind ausgezeichnet und es lohnt sich zu lesen. Es wird darauf hingewiesen, dass Clang den Fall in dem Artikel
-fsanitize=undefined
erfasst hat, mit dem ich ihn für diesen Fall nicht reproduzieren kann, aber mit gcc-fsanitize=undefined
( siehe live ). Der wahrscheinlich berüchtigtste Fehler bei einem Optimierer, der auf undefiniertes Verhalten schließen lässt, ist das Entfernen der Nullzeigerprüfung des Linux-Kernels .Obwohl dies eine aggressive Optimierung ist, ist es wichtig zu beachten, dass, wie der C ++ - Standard sagt, undefiniertes Verhalten ist:
Was im Wesentlichen bedeutet, dass alles möglich ist und es bemerkt ( Hervorhebung von mir ):
Um eine Warnung von gcc zu erhalten, müssen wir die
cout
Außenseite der Schleife verschieben und dann sehen wir die folgende Warnung ( sehen Sie sie live ):warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations] for(di=0; di<4;di++,delta=mc[di]){ } ^
Dies hätte wahrscheinlich ausgereicht, um das OP mit genügend Informationen zu versorgen, um herauszufinden, was vor sich ging. Solche Inkonsistenzen sind typisch für die Verhaltensweisen, die wir bei undefiniertem Verhalten beobachten können. Um ein besseres Verständnis dafür zu erhalten, warum solche Warnungen angesichts undefinierten Verhaltens inkonsistent sein können, warum können Sie bei der Optimierung aufgrund undefinierten Verhaltens nicht warnen? ist eine gute Lektüre.
Hinweis,
-fno-aggressive-loop-optimizations
ist in den Versionshinweisen zu gcc 4.8 dokumentiert .quelle
g++ -Waggressive-loop-optimizations -Wall -Wextra -O3 test.cpp
- Laut dieser Seite sollte der Compiler eine Warnung anzeigen: gcc.gnu.org/onlinedocs/gcc/Warning-Options.htmlDa Sie inkrementieren,
di
bevor Sie es zum Indizieren verwendenmc
, verweisen Sie beim vierten Mal durch die Schleife auf mc [4], das sich hinter dem Ende Ihres Arrays befindet, was wiederum zu störendem Verhalten führen kann.quelle
di++,delta=mc[di-1]
oderdelta=mc[di],di++
behebt das Problem. Sieht aus wie Logicrat ist richtig.delta=mc[di++]
nichtdelta=mc[++di]
allemc
Werte verwenden?Dies liegt daran, dass di ++ beim letzten Durchlauf der Schleife ausgeführt wird.
Zum Beispiel;
int di = 0; for(; di < 4; di++); // after the loop di == 4 // (inside the loop we see 0,1,2,3) // (inside the for statement, after di++, we see 1,2,3,4)
Sie greifen auf mc [] zu, wenn di == 4 ist. Dies ist also ein Problem außerhalb der Grenzen, das möglicherweise einen Teil des Stapels zerstört und die Variable di beschädigt.
Eine Lösung wäre:
for(int di = 0; di < 4; di++) { cout << di << endl; delta = mc[di]; }
quelle
Du hast das:
for(int di=0; di<4; di++, delta=mc[di]) { cout<<di<<endl; }
Versuchen Sie stattdessen Folgendes:
for(int di=0; di<4; delta=mc[di++]) { cout<<di<<endl; }
BEARBEITEN:
Um zu klären, was los ist, brechen wir die Iteration Ihrer For-Schleife auf:
1. Iteration: Anfangs ist di auf 0 gesetzt. Vergleichsprüfung: Ist di kleiner als 4? Ja, okay, fahren Sie fort. Inkrementiere di um 1. Jetzt ist di = 1. Nimm das "n-te" Element von mc [] und setze es auf Delta. Dieses Mal greifen wir zum 2. Element, da dieser indizierte Wert 1 und nicht 0 ist. Führen Sie schließlich die Codeblöcke in der for-Schleife aus.
2. Iteration: Jetzt ist di auf 1 gesetzt. Vergleichsprüfung: Ist di kleiner als 4? Ja und weiter. Inkrementiere di um 1. Jetzt ist di = 2. Nimm das "n-te" Element von mc [] und setze es auf Delta. Dieses Mal greifen wir zum 3. Element, da dieser indizierte Wert 2 ist. Führen Sie schließlich die Codeblöcke in der for-Schleife aus.
3. Iteration: Jetzt ist di auf 2 gesetzt. Vergleichsprüfung: Ist di kleiner als 4? Ja und weiter. Inkrementiere di um 1. Jetzt ist di = 3. Nimm das "n-te" Element von mc [] und setze es auf Delta. Dieses Mal greifen wir zum 4. Element, da dieser indizierte Wert 3 ist. Führen Sie schließlich die Codeblöcke in der for-Schleife aus.
4. Iteration: Jetzt ist di auf 3 gesetzt. Vergleichsprüfung: Ist di kleiner als 4? Ja und weiter. Inkrementiere di um 1. Jetzt ist di = 4. (Kannst du sehen, wohin das führt?) Nimm das "n-te" Element von mc [] und setze es auf Delta. Dieses Mal greifen wir zum 5. Element, da dieser indizierte Wert 4 ist. Oh, wir haben ein Problem; Unsere Array-Größe beträgt nur 4. Delta hat jetzt Müll und dies ist undefiniertes Verhalten oder Korruption. Führen Sie abschließend die Codeblöcke in der for-Schleife mit "Garbage Delta" aus.
5. Iteration. Jetzt ist di auf 4 gesetzt. Vergleichsprüfung: Ist di kleiner als 4? Nein, aus der Schleife ausbrechen.
Beschädigung durch Überschreiten der Grenzen des zusammenhängenden Speichers (Array).
quelle