Ich schreibe ein C ++ - Programm, das nicht funktioniert (ich bekomme einen Segmentierungsfehler), wenn ich es mit Optimierungen kompiliere (Optionen -O1, -O2, -O3 usw.), aber es funktioniert gut, wenn ich kompilieren Sie es ohne Optimierungen.
Gibt es eine Chance, dass der Fehler in meinem Code ist? oder sollte ich annehmen, dass dies ein Fehler in GCC ist?
Meine GCC-Version ist 3.4.6.
Gibt es eine bekannte Problemumgehung für diese Art von Problem?
Es gibt einen großen Geschwindigkeitsunterschied zwischen der optimierten und der nicht optimierten Version meines Programms, also muss ich wirklich Optimierungen verwenden.
Das ist mein ursprünglicher Funktor. Der, der ohne Optimierungsebenen gut funktioniert und einen Segmentierungsfehler mit jeder Optimierungsebene auslöst:
%Vor%Und dieses funktioniert tadellos mit jedem Niveau der Optimierung:
%Vor%Leider ist dieses Problem schwer zu reproduzieren, weil es mit bestimmten Werten passiert. Ich bekomme den Segmentierungsfehler beim Sortieren von nur einem von mehr als tausend Vektoren, also hängt es wirklich von der spezifischen Kombination von Werten ab, die jeder Vektor hat.
Nachdem Sie nun das Codefragment gepostet haben und eine funktionierende Problemumgehung gefunden wurde (@Windows-Programmierer-Antwort), kann ich sagen, dass Sie vielleicht nach -ffloat-store
suchen.
-float-store
Speichern Sie Gleitkomma-Variablen nicht in Registern und verhindern Sie andere Optionen, die sich ändern könnten, wenn ein Fließkommawert aus einem Register oder Speicher entnommen wird.
Diese Option verhindert eine unerwünschte Überpräzision auf Maschinen wie dem 68000, wo die Floating-Register (des 68881) mehr Präzision als ein Doppel haben. Ähnlich für die x86-Architektur. Für die meisten Programme ist die Exzess-Genauigkeit nur gut, aber einige Programme verlassen sich auf die genaue Definition von IEEE-Gleitkomma. Verwenden Sie -ffloat-store für solche Programme, nachdem Sie sie modifiziert haben, um alle relevanten Zwischenberechnungen in Variablen zu speichern.
Quelle: Ссылка
Ich nehme an, dass Ihr Code zuerst falsch ist.
Obwohl es schwer zu sagen ist.
Kompiliert Ihr Code mit 0 Warnungen?
%Vor%Hier ist ein Code, der scheint zu funktionieren, bis Sie -O3 ...
drücken %Vor%Ohne Optimierungen bekomme ich "2 1 0"; mit Optimierungen bekomme ich "40 1 2293680". Warum? Weil ich und k optimiert wurden!
Aber ich nahm die Adresse von j und ging aus dem Speicherbereich, der j zugewiesen war. Das ist vom Standard nicht erlaubt. Es ist sehr wahrscheinlich, dass Ihr Problem durch eine ähnliche Abweichung vom Standard verursacht wird.
Ich finde valgrind ist oft hilfreich in Zeiten wie diesen.
EDIT: Einige Kommentatoren haben den Eindruck, dass der Standard beliebige Zeigerarithmetik erlaubt. Tut es nicht. Denken Sie daran, dass einige Architekturen seltsame Adressierungsschemata haben, die Ausrichtung wichtig sein kann und Sie Probleme bekommen können, wenn Sie bestimmte Register überlaufen lassen!
Die Wörter des Standards [Entwurf] zum Hinzufügen / Subtrahieren einer Ganzzahl zu / von einem Zeiger (Hervorhebung hinzugefügt):
"Wenn sowohl der Zeigeroperand als auch das Ergebnis auf Elemente desselben Arrayobjekts oder eines nach dem letzten Element des Arrayobjekts zeigen, soll die Auswertung keinen Überlauf erzeugen; Andernfalls ist das Verhalten nicht definiert. "
Da & amp; j nicht einmal auf ein Array-Objekt zeigt, können & amp; j-1 und & amp; j + 1 kaum auf einen Teil desselben Array-Objekts zeigen. Also einfach & amp; j + 1 zu evaluieren (ganz zu schweigen davon, es zu dereferenzieren) ist undefiniertes Verhalten.
Auf x86 können wir ziemlich sicher sein, dass das Hinzufügen von einem zu einem Zeiger ziemlich sicher ist und uns einfach zum nächsten Speicherort bringt. Im obigen Code tritt das Problem auf, wenn wir Annahmen darüber treffen, was der Speicher enthält, was natürlich nicht der Standard ist.
Versuchen Sie als Experiment, ob dies den Compiler dazu zwingt, alles konsistent zu runden.
%Vor% Der Fehler ist in Ihrem Code. Es ist wahrscheinlich, dass Sie etwas tun, das undefiniertes Verhalten gemäß dem C-Standard aufruft, der zufällig ohne Optimierungen arbeitet, aber wenn GCC bestimmte Annahmen für die Durchführung seiner Optimierungen trifft, bricht der Code, wenn diese Annahmen nicht zutreffen. Stellen Sie sicher, dass Sie mit der Option -Wall
kompilieren, und dass -Wextra
ebenfalls eine gute Idee ist, und prüfen Sie, ob Sie Warnungen erhalten. Sie könnten auch -ansi
oder -pedantic
versuchen, aber diese ergeben wahrscheinlich falsche positive Ergebnisse.
Es ist sehr selten der Compiler-Fehler, aber Compiler haben Bugs in ihnen, und sie manifestieren sich oft auf verschiedenen Optimierungsebenen (wenn es beispielsweise einen Fehler in einem Optimierungspass gibt).
Im Allgemeinen beim Melden von Programmierproblemen: Geben Sie ein minimales Codebeispiel an, um das Problem zu demonstrieren , sodass Benutzer den Code einfach in einer Datei speichern, kompilieren und ausführen können. Machen Sie es so einfach wie möglich , um Ihr Problem zu reproduzieren.
Probiere auch verschiedene Versionen von GCC aus (das Kompilieren deines eigenen GCC ist sehr einfach, besonders unter Linux). Wenn möglich, versuchen Sie es mit einem anderen Compiler. Intel C hat einen Compiler, der mehr oder weniger GCC-kompatibel ist (und frei für nicht-kommerzielle Nutzung, denke ich). Dies wird helfen, das Problem zu lokalisieren.
Es ist fast ( fast ) nie der Compiler.
Stellen Sie zunächst sicher, dass Sie mit "-Wall" eine Warnung erstellen.
Wenn Ihnen das keinen "Eureka" -Moment beschert, hängen Sie einen Debugger an die am wenigsten optimierte Version Ihrer ausführbaren Datei an, die abstürzt und sieht, was sie macht und wohin sie geht.
5 bringt Ihnen 10, dass Sie das Problem an diesem Punkt behoben haben.
Vor ein paar Tagen kam das gleiche Problem auf, in meinem Fall war es Aliasing. Und GCC macht es anders, aber nicht falsch, wenn es mit anderen Compilern verglichen wird. GCC ist zu etwas geworden, was man als Regel-Anwalt des C ++ - Standards bezeichnen könnte, und ihre Implementierung ist korrekt, aber Sie müssen auch wirklich in C ++ richtig sein, oder es wird etwas optimieren, was ein Schmerz ist. Aber du bekommst Geschwindigkeit, also kann ich mich nicht beschweren.
Ich erwarte einige downvotes, nachdem ich einige der Kommentare gelesen habe, aber in der Welt der Konsolenspiel-Programmierung ist es ziemlich allgemein bekannt, dass die höheren Optimierungsebenen manchmal in seltsamen Randfällen falschen Code erzeugen können. Es kann sehr gut sein, dass Randfälle mit subtilen Änderungen am Code behoben werden können.
Alles klar ... Das ist eines der merkwürdigsten Probleme, die ich je hatte Ich glaube nicht, dass ich genug Beweise habe, um zu sagen, dass es ein GCC-Fehler ist, aber ehrlich gesagt ... sieht es wirklich wie eins aus.
Das ist mein ursprünglicher Funktor. Der, der ohne Optimierungsebenen gut funktioniert und einen Segmentierungsfehler mit jeder Optimierungsebene auslöst:
%Vor%Und dieses funktioniert tadellos mit jedem Niveau der Optimierung:
%Vor%Leider ist dieses Problem schwer zu reproduzieren, weil es mit bestimmten Werten passiert. Ich bekomme den Segmentierungsfehler beim Sortieren von nur einem von mehr als tausend Vektoren, also hängt es wirklich von der spezifischen Kombination von Werten ab, die jeder Vektor hat.
Wow, ich habe nicht so schnell Antworten erwartet und so viele ...
Der Fehler tritt auf, wenn ein Zeiger std :: vector mit std :: sort ()
sortiert wirdIch biete den strikt-schwach-ordnenden Funktor.
Aber ich weiß, dass der Funktor, den ich anbiete, korrekt ist, weil ich ihn oft benutzt habe und es gut funktioniert.
Außerdem kann der Fehler kein ungültiger Zeiger im Vektor sein, weil der Fehler gerade auftritt, wenn ich den Vektor sortiere. Wenn ich durch den Vektor iteriere, ohne zuerst std :: sort zu verwenden, funktioniert das Programm gut.
Ich habe gerade GDB benutzt, um herauszufinden, was los ist. Der Fehler tritt auf, wenn std :: sort meinen Funktor aufruft. Aparently std :: sort übergibt einen ungültigen Zeiger an meinen Funktor. (Dies geschieht natürlich nur mit der optimierten Version, jeder Optimierungsstufe -O, -O2, -O3)
wie andere darauf hingewiesen haben, wahrscheinlich strenges Aliasing. drehe es in o3 und versuche es erneut. Meine Vermutung ist, dass Sie einige Zeiger Tricks in Ihrem Funktor (Fast Float als Int vergleichen? Objekttyp in niedrigeren 2 Bits?), Die über Inline-Template-Funktionen fehlschlagen. Warnungen helfen nicht dabei, diesen Fall zu erfassen. "Wenn der Compiler alle strikten Alias-Probleme entdecken könnte, könnte er sie genauso vermeiden." Wenn nur eine nicht verwandte Codezeile geändert wird, kann das Problem auftreten oder verschwinden, wenn sich die Registerzuordnung ändert.
Da die aktualisierte Frage angezeigt wird;), existiert das Problem mit std::vector<T*>
. Ein häufiger Fehler bei Vektoren ist reserve (), was hätte sein sollen resize () d. Daher schreiben Sie außerhalb der Array-Grenzen. Ein Optimierer kann diese Schreibvorgänge verwerfen.
posten Sie den Code in der Entfernung! es macht wahrscheinlich eine Zeigermagie, siehe meinen vorherigen Beitrag. Durch eine Zwischenzuweisung wird der Fehler in Ihrem Code durch Ändern der Registerzuweisung ausgeblendet. noch mehr davon ist die Ausgabe, die Dinge ändert!
Die wahre Antwort ist irgendwo in den Kommentaren in diesem Thread versteckt. Vor allem: Es ist kein Fehler im Compiler.
Das Problem hat mit Gleitkomma-Genauigkeit zu tun. distanceToPointSort
sollte eine Funktion sein, die für beide Argumente (a, b) und (b, a) niemals true zurückgibt, aber genau das kann passieren, wenn der Compiler entscheidet, für einige Datenpfade eine höhere Genauigkeit zu verwenden. Das Problem ist besonders wahrscheinlich, aber keineswegs beschränkt auf x86 ohne -mfpmath=sse
. Wenn sich der Komparator so verhält, kann die Funktion sort
verwechselt werden, und der Segmentierungsfehler ist nicht überraschend.
Ich halte -ffloat-store
für die beste Lösung (bereits von CesarB vorgeschlagen).
Tags und Links compilation c++ gcc