Hin und wieder funktioniert C ++ - Code nicht, wenn er mit einer gewissen Optimierungsstufe kompiliert wird. Es kann sich um einen Compiler handeln, der eine Optimierung durchführt, die den Code bricht, oder um Code, der ein undefiniertes Verhalten enthält, das es dem Compiler ermöglicht, das zu tun, was er für richtig hält.
Angenommen, ich habe einen Code, der beim Kompilieren nur mit höheren Optimierungsstufen kaputt geht. Woher weiß ich, ob es sich um den Code oder den Compiler handelt und was mache ich, wenn es sich um den Compiler handelt?
optimization
compiler
scharfer Zahn
quelle
quelle
Antworten:
Ich würde sagen, es ist eine sichere Wette, dass in den allermeisten Fällen Ihr Code und nicht der Compiler kaputt ist. Und selbst in Ausnahmefällen, in denen es sich um den Compiler handelt, verwenden Sie wahrscheinlich eine undurchsichtige Sprachfunktion auf ungewöhnliche Weise, auf die der jeweilige Compiler nicht vorbereitet ist. Mit anderen Worten, Sie könnten wahrscheinlich Ihren Code so ändern, dass er idiomatischer ist und die Schwachstelle des Compilers vermieden wird.
Wenn Sie nachweisen können , dass Sie einen Compiler-Fehler gefunden haben (basierend auf der Sprachspezifikation), melden Sie dies auf jeden Fall den Compiler-Entwicklern, damit sie es irgendwann reparieren können.
quelle
Genau wie bei allen anderen Bugs üblich: Führen Sie ein kontrolliertes Experiment durch. Grenzen Sie den verdächtigen Bereich ein, deaktivieren Sie die Optimierungen für alles andere und variieren Sie die Optimierungen, die auf diesen Codeabschnitt angewendet werden. Sobald Sie eine 100% ige Reproduzierbarkeit erreicht haben, können Sie Ihren Code variieren und Dinge einführen, die bestimmte Optimierungen beeinträchtigen könnten (z. B. mögliches Pointer-Aliasing, externe Aufrufe mit möglichen Nebenwirkungen usw.). Ein Blick auf den Assembler-Code in einem Debugger könnte ebenfalls hilfreich sein.
quelle
Untersuchen Sie den resultierenden Assemblycode, und prüfen Sie, ob er den Anforderungen Ihrer Quelle entspricht. Denken Sie daran, dass die Chancen sehr hoch sind, dass wirklich Ihr Code in nicht offensichtlicher Weise schuld ist.
quelle
In über 30 Jahren Programmierzeit habe ich immer noch nur etwa 10 echte Compiler-Fehler (Fehler bei der Codegenerierung) gefunden. Die Anzahl meiner eigenen (und der anderer) Fehler, die ich im selben Zeitraum gefunden und behoben habe, ist wahrscheinlich > 10.000. Meine "Faustregel" lautet dann, dass die Wahrscheinlichkeit, dass ein Fehler vom Compiler verursacht wird, <0,001 ist.
quelle
Ich fing an, einen Kommentar zu schreiben und entschied dann, dass es zu lang und zu viel ist, um es auf den Punkt zu bringen.
Ich würde argumentieren, dass es Ihr Code ist, der gebrochen ist. In dem unwahrscheinlichen Fall, dass Sie einen Fehler im Compiler entdeckt haben, sollten Sie ihn den Compiler-Entwicklern melden, aber hier endet der Unterschied.
Die Lösung besteht darin, das fehlerhafte Konstrukt zu identifizieren und neu zu faktorisieren, damit es dieselbe Logik auf unterschiedliche Weise ausführt. Das würde höchstwahrscheinlich das Problem lösen, egal ob der Fehler auf Ihrer Seite oder im Compiler liegt.
quelle
quelle
int + int
dem ein Überlauf abhing , genau so, als würde er zu einem Hardware-ADD-Befehl kompiliert. Es funktionierte einwandfrei, wenn es mit einer älteren Version von GCC kompiliert wurde, aber nicht, wenn es mit dem neueren Compiler kompiliert wurde. Anscheinend haben die netten Leute bei GCC entschieden, dass ihr Optimierer unter der Annahme arbeiten könnte, dass dies niemals der Fall ist, da das Ergebnis eines Ganzzahlüberlaufs undefiniert ist. Es hat einen wichtigen Zweig direkt aus dem Code heraus optimiert.Wenn Sie wissen möchten, ob es sich um Ihren Code oder den Compiler handelt, müssen Sie die Spezifikation von C ++ genau kennen.
Wenn der Zweifel bestehen bleibt, müssen Sie die x86-Assembly genau kennen.
Wenn Sie nicht in der Stimmung sind, beides perfekt zu lernen, ist es mit ziemlicher Sicherheit ein undefiniertes Verhalten, das Ihr Compiler je nach Optimierungsstufe unterschiedlich auflöst.
quelle
Es ist wahrscheinlicher, dass ein Kompilierungsfehler im Standardcode oder ein interner Kompilierungsfehler auftritt, als dass Optimierer falsch liegen. Aber ich habe von Compilern gehört, die Schleifen falsch optimieren und dabei einige Nebenwirkungen, die eine Methode verursachen, vergessen.
Ich habe keine Vorschläge, wie man weiß, ob es Sie oder der Compiler ist. Sie können einen anderen Compiler versuchen.
Eines Tages fragte ich mich, ob es mein Code war oder nicht und jemand schlug mir Valgrind vor. Ich habe die 5 oder 10 Minuten damit verbracht, mein Programm damit auszuführen (glaube ich)
valgrind --leak-check=yes myprog arg1 arg2
habe es getan, aber ich habe mit anderen Optionen gespielt) und es hat mir sofort EINE Zeile gezeigt, die unter einem bestimmten Fall ausgeführt wird, der das Problem war. Dann lief meine App reibungslos, ohne seltsame Abstürze, Fehler oder seltsames Verhalten. Valgrind oder ein anderes Tool wie es ist ein guter Weg, um zu wissen, ob es Ihr Code ist.Randnotiz: Ich habe mich einmal gefragt, warum die Leistung meiner App nicht stimmt. Es stellte sich heraus, dass alle meine Leistungsprobleme auch in einer Zeile standen. Ich schrieb
for(int i=0; i<strlen(sz); ++i) {
. Die sz war ein paar mb. Aus irgendeinem Grund lief der Compiler auch nach der Optimierung jedes Mal länger. Eine Zeile kann eine große Sache sein. Von Auftritten bis zu Abstürzenquelle
Immer häufiger kommt es vor, dass Compiler den Code für Dialekte von C unterbrechen, die Verhaltensweisen unterstützen, die nicht vom Standard vorgeschrieben sind, und es zulassen, dass Code, der auf diese Dialekte abzielt, effizienter ist, als es streng konformer Code sein könnte. In einem solchen Fall wäre es unfair, den Compiler, der einen Dialekt verarbeitet, der die erforderliche Semantik nicht unterstützt, als "fehlerhaften" Code zu bezeichnen, der für Compiler, die den Zieldialekt implementieren, 100% zuverlässig ist . Stattdessen rühren die Probleme einfach von der Tatsache her, dass die Sprache, die von modernen Compilern mit aktivierten Optimierungen verarbeitet wird, von Dialekten abweicht, die früher populär waren (und von vielen Compilern mit deaktivierten Optimierungen oder sogar mit aktivierten Optimierungen noch verarbeitet werden).
Beispielsweise wird viel Code für Dialekte geschrieben, die eine Reihe von Zeiger-Aliasing-Mustern als legitim erkennen, die nicht durch die gcc-Interpretation des Standards vorgeschrieben sind, und solche Muster verwenden, um eine einfache Übersetzung des Codes lesbarer und effizienter zu machen als es nach der Interpretation des C-Standards durch gcc möglich wäre. Solcher Code ist möglicherweise nicht mit gcc kompatibel, aber das bedeutet nicht, dass er kaputt ist. Es basiert einfach auf Erweiterungen, die gcc nur mit deaktivierten Optimierungen unterstützt.
quelle
Isolieren Sie die problematische Stelle und vergleichen Sie das beobachtete Verhalten mit dem, was gemäß der Sprachspezifikation geschehen soll. Auf jeden Fall nicht einfach, aber das müssen Sie tun, um zu wissen (und nicht nur anzunehmen ).
Ich wäre wahrscheinlich nicht so akribisch. Ich würde eher das Support-Forum / die Mailing-Liste des Compiler-Herstellers fragen. Wenn es sich wirklich um einen Fehler im Compiler handelt, können sie ihn möglicherweise beheben. Wahrscheinlich wäre es sowieso mein Code. Zum Beispiel können Sprachspezifikationen in Bezug auf die Speichersichtbarkeit beim Threading sehr unerklärlich sein und können nur bei Verwendung bestimmter Optimierungsflags auf einer bestimmten Hardware (!) Sichtbar werden. Einige Verhaltensweisen können von der Spezifikation undefiniert sein, so dass sie mit einigen Compilern / einigen Flags und mit anderen nicht funktionieren können.
quelle
Höchstwahrscheinlich weist Ihr Code ein undefiniertes Verhalten auf (wie andere erklärt haben, sind Fehler in Ihrem Code viel wahrscheinlicher als im Compiler, selbst wenn C ++ - Compiler so komplex sind, dass sie Fehler aufweisen; selbst die C ++ - Spezifikation weist Entwurfsfehler auf). . Und UB kann hier sein, auch wenn die kompilierte ausführbare Datei zufällig funktioniert (durch Pech).
Lesen Sie also Lattners Blog Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte (das meiste davon gilt auch für C ++ 11).
Das Valgrind- Tool und die neuesten
-fsanitize=
Instrumentierungsoptionen für GCC (oder Clang / LLVM ) sollten ebenfalls hilfreich sein. Und natürlich alle Warnungen aktivieren:g++ -Wall -Wextra
quelle