Der C11-Standard scheint zu implizieren, dass Iterationsanweisungen mit konstanten Steuerausdrücken nicht optimiert werden sollten. Ich nehme meinen Rat von dieser Antwort , in der Abschnitt 6.8.5 des Standardentwurfs ausdrücklich zitiert wird:
Eine Iterationsanweisung, deren steuernder Ausdruck kein konstanter Ausdruck ist, kann von der Implementierung als beendet angenommen werden.
In dieser Antwort wird erwähnt, dass eine Schleife wie while(1) ;
nicht optimiert werden sollte.
Also ... warum optimiert Clang / LLVM die folgende Schleife (kompiliert mit cc -O2 -std=c11 test.c -o test
)?
#include <stdio.h>
static void die() {
while(1)
;
}
int main() {
printf("begin\n");
die();
printf("unreachable\n");
}
Auf meinem Computer wird dies ausgedruckt und stürztbegin
dann bei einer illegalen Anweisung ab (eine ud2
nachgestellte Falle die()
). Auf godbolt können wir sehen, dass nach dem Aufruf von nichts generiert wird puts
.
Es war eine überraschend schwierige Aufgabe, Clang dazu zu bringen, eine Endlosschleife auszugeben -O2
- während ich wiederholt eine volatile
Variable testen konnte, die einen Speicherlesevorgang beinhaltet, den ich nicht möchte. Und wenn ich so etwas mache:
#include <stdio.h>
static void die() {
while(1)
;
}
int main() {
printf("begin\n");
volatile int x = 1;
if(x)
die();
printf("unreachable\n");
}
... Klirren Sie Drucke, begin
gefolgt von unreachable
der Endlosschleife.
Wie bringt man Clang dazu, eine ordnungsgemäße Endlosschleife ohne Speicherzugriff mit aktivierten Optimierungen auszugeben?
quelle
exit()
, und weil der Code möglicherweise eine Situation entdeckt hat, in der nicht garantiert werden kann, dass die Auswirkungen der fortgesetzten Ausführung nicht schlimmer als nutzlos sind . Eine Jump-to-Self-Schleife ist eine ziemlich miese Art, mit solchen Situationen umzugehen, aber sie kann dennoch die beste Art sein, mit einer schlechten Situation umzugehen.Antworten:
Der C11-Standard sagt dies, 6.8.5 / 6:
Die beiden Fußnoten sind nicht normativ, bieten jedoch nützliche Informationen:
In Ihrem Fall
while(1)
handelt es sich um einen kristallklaren konstanten Ausdruck, sodass die Implementierung möglicherweise nicht davon ausgeht , dass er beendet wird. Eine solche Implementierung wäre hoffnungslos kaputt, da "für immer" Schleifen ein übliches Programmierkonstrukt sind.Was mit dem "nicht erreichbaren Code" nach der Schleife passiert, ist meines Wissens jedoch nicht genau definiert. Allerdings verhält sich Clang in der Tat sehr seltsam. Vergleichen des Maschinencodes mit gcc (x86):
gcc 9.2
-O3 -std=c11 -pedantic-errors
clang 9.0.0
-O3 -std=c11 -pedantic-errors
gcc generiert die Schleife, clang rennt einfach in den Wald und verlässt mit Fehler 255.
Ich neige zu diesem nicht konformen Verhalten von Clang. Weil ich versucht habe, Ihr Beispiel so weiter auszubauen:
Ich habe C11 hinzugefügt
_Noreturn
, um dem Compiler weiter zu helfen. Es sollte klar sein, dass diese Funktion allein aufgrund dieses Schlüsselworts auflegt.setjmp
wird bei der ersten Ausführung 0 zurückgeben, daher sollte dieses Programm nur in diewhile(1)
und dort anhalten und nur "begin" drucken (vorausgesetzt, \ n löscht stdout). Dies geschieht mit gcc.Wenn die Schleife einfach entfernt wurde, sollte sie zweimal "begin" und dann "nicht erreichbar" drucken. Beim Klirren ( Godbolt ) wird jedoch 1 Mal "begin" und dann "unerreichbar" ausgegeben , bevor der Exit-Code 0 zurückgegeben wird. Das ist einfach falsch, egal wie Sie es ausdrücken.
Ich kann hier keinen Grund finden, undefiniertes Verhalten zu behaupten, daher gehe ich davon aus, dass dies ein Fehler im Klirren ist. In jedem Fall macht dieses Verhalten das Klirren für Programme wie eingebettete Systeme zu 100% nutzlos, bei denen Sie sich einfach auf ewige Schleifen verlassen müssen, in denen das Programm hängt (während Sie auf einen Watchdog warten usw.).
quelle
6.8.5/6
in der Form von wenn (diese), dann können Sie (dies) annehmen . Das heißt nicht, wenn nicht (diese), können Sie (dies) nicht annehmen . Es ist eine Spezifikation nur, wenn die Bedingungen erfüllt sind, nicht wenn sie nicht erfüllt sind, wo Sie mit den Standards tun können, was Sie wollen. Und wenn es keine Observablen gibt ...int z=3; int y=2; int x=1; printf("%d %d\n", x, z);
es2
in der Baugruppe keine gibt , so dass im leeren sinnlosen Sinnex
nicht nach,y
sondern nachz
der Optimierung zugewiesen wurde . Ausgehend von Ihrem letzten Satz folgen wir den regulären Regeln, gehen davon aus, dass die Zeit angehalten wurde (weil wir nicht besser eingeschränkt wurden) und belassen den endgültigen, "nicht erreichbaren" Druck. Jetzt optimieren wir diese nutzlose Aussage (weil wir es nicht besser wissen).while(1);
dasselbe wie eineint y = 2;
Aussage darüber, welche Semantik wir optimieren dürfen, auch wenn ihre Logik in der Quelle verbleibt. Ab n1528 hatte ich den Eindruck, dass sie gleich sein könnten, aber da die Leute viel erfahrener als ich anders argumentieren und es anscheinend ein offizieller Fehler ist, dann jenseits einer philosophischen Debatte darüber, ob der Wortlaut in der Norm explizit ist wird das Argument strittig gemacht.Sie müssen einen Ausdruck einfügen, der eine Nebenwirkung verursachen kann.
Die einfachste Lösung:
Godbolt Link
quelle
asm("")
ist implizitasm volatile("");
und daher muss die asm-Anweisung so oft ausgeführt werden wie in der abstrakten Maschine gcc.gnu.org/onlinedocs/gcc/Basic-Asm.html . (Beachten Sie, dass es nicht sicher ist, dass die Nebenwirkungen Speicher oder Register enthalten. Sie benötigen Extended ASM mit einem"memory"
Clobber, wenn Sie Speicher lesen oder schreiben möchten, auf den Sie jemals über C zugreifen. Basic ASM ist nur für Dinge wieasm("mfence")
oder sichercli
.)Andere Antworten betrafen bereits Möglichkeiten, wie Clang die Endlosschleife mit Inline-Assemblersprache oder anderen Nebenwirkungen emittieren kann. Ich möchte nur bestätigen, dass dies tatsächlich ein Compiler-Fehler ist. Insbesondere handelt es sich um einen langjährigen LLVM-Fehler. Er wendet das C ++ - Konzept "Alle Schleifen ohne Nebenwirkungen müssen beendet werden" auf Sprachen an, in denen dies nicht der Fall sein sollte, z. B. C.
Beispielsweise erlaubt die Programmiersprache Rust auch Endlosschleifen und verwendet LLVM als Backend, und es gibt dasselbe Problem.
Kurzfristig scheint LLVM weiterhin davon auszugehen, dass "alle Schleifen ohne Nebenwirkungen beendet werden müssen". Für jede Sprache, die Endlosschleifen zulässt, erwartet LLVM, dass das Front-End
llvm.sideeffect
Opcodes in solche Schleifen einfügt. Dies ist, was Rust plant, also wird Clang (beim Kompilieren von C-Code) wahrscheinlich auch das tun müssen.quelle
sideeffect
(im Jahr 2017) und erwartet, dass Frontends diese Operation nach eigenem Ermessen in Schleifen einfügen. LLVM musste einige Standardeinstellungen für Schleifen auswählen, und es wurde zufällig diejenige ausgewählt, die absichtlich oder auf andere Weise mit dem Verhalten von C ++ übereinstimmt. Natürlich müssen noch einige Optimierungsarbeiten durchgeführt werden, z. B. das Zusammenführen aufeinanderfolgendersideeffect
Operationen zu einer. (Dies ist es, was das Rust-Front-End daran hindert, es zu verwenden.) Auf dieser Basis befindet sich der Fehler im Front-End (Clang), das die Operation nicht in Schleifen einfügt.sideeffect
Ops zum Start jeder Funktion und sah keine Regression der Laufzeitleistung. Das einzige Problem ist eine Regression der Kompilierungszeit , anscheinend aufgrund der fehlenden Fusion aufeinanderfolgender Operationen, wie ich in meinem vorherigen Kommentar erwähnt habe.Dies ist ein Clang-Fehler
... beim Inlinen einer Funktion mit einer Endlosschleife. Das Verhalten ist anders, wenn es
while(1);
direkt in main erscheint, was für mich sehr fehlerhaft riecht.Siehe @ Arnavion Antwort für eine Zusammenfassung und Links. Der Rest dieser Antwort wurde geschrieben, bevor ich die Bestätigung erhielt, dass es sich um einen Fehler handelte, geschweige denn um einen bekannten Fehler.
Um die Titelfrage zu beantworten: Wie erstelle ich eine Endlosschleife, die nicht optimiert werden kann? ? -
Erstellen Sie
die()
ein Makro und keine Funktion , um diesen Fehler in Clang 3.9 und höher zu umgehen. (Frühere Clang-Versionen behalten entweder die Schleife bei oder geben einecall
an eine Nicht-Inline-Version der Funktion mit der Endlosschleife aus.) Dies scheint sicher zu sein, selbst wenn dieprint;while(1);print;
Funktion in ihren Aufrufer ( Godbolt ) eingebunden ist .-std=gnu11
vs.-std=gnu99
ändert nichts.Wenn Sie sich nur für GNU C interessieren
__asm__("");
, funktioniert auch P__J __ in der Schleife und sollte die Optimierung des umgebenden Codes für Compiler, die ihn verstehen , nicht beeinträchtigen . GNU C Basic asm-Anweisungen sind implizitvolatile
, daher gilt dies als sichtbarer Nebeneffekt, der so oft "ausgeführt" werden muss wie in der abstrakten C-Maschine. (Und ja, Clang implementiert den GNU-Dialekt von C, wie im GCC-Handbuch dokumentiert.)Einige Leute haben argumentiert, dass es legal sein könnte, eine leere Endlosschleife zu optimieren. Ich bin nicht einverstanden 1 , aber selbst wenn wir das akzeptieren, kann es nicht auch legal sein für Clang Aussagen nach der Schleife nicht erreichbar sind , anzunehmen, und die Ausführung fällt vom Ende der Funktion in die nächste Funktion oder in Müll lassen das dekodiert als zufällige Anweisungen.
(Das wäre standardkonform für Clang ++ (aber immer noch nicht sehr nützlich); Endlosschleifen ohne Nebenwirkungen sind UB in C ++, aber nicht C.
Is while (1); undefiniertes Verhalten in C? UB lässt den Compiler grundsätzlich alles ausgeben Für Code auf einem Ausführungspfad, der definitiv auf UB trifft. Eine
asm
Anweisung in der Schleife würde diese UB für C ++ vermeiden. In der Praxis entfernt Clang-Kompilierung als C ++ jedoch keine unendlichen leeren Schleifen mit konstantem Ausdruck, außer beim Inlining, genau wie bei Kompilieren als C.)Durch manuelles Inlining wird
while(1);
geändert, wie Clang es kompiliert: Endlosschleife in asm vorhanden. Dies ist, was wir von einem POV für Regeljuristen erwarten würden.Auf dem Godbolt-Compiler-Explorer wird Clang 9.0 -O3 als C (
-xc
) für x86-64 kompiliert :Derselbe Compiler mit denselben Optionen kompiliert einen
main
,infloop() { while(1); }
der zuerst denselben aufruftputs
, dann abermain
nach diesem Punkt keine Anweisungen mehr ausgibt. Wie gesagt, die Ausführung fällt einfach vom Ende der Funktion in die nächste Funktion (aber der Stapel ist für die Funktionseingabe falsch ausgerichtet, sodass es sich nicht einmal um einen gültigen Tailcall handelt).Die gültigen Optionen wären zu
label: jmp label
Endlosschleife ausgebenreturn 0
vonmain
.Ein Absturz oder eine andere Fortsetzung ohne Drucken von "nicht erreichbar" ist für eine C11-Implementierung eindeutig nicht in Ordnung, es sei denn, es gibt UB, die ich nicht bemerkt habe.
Fußnote 1:
Für die Aufzeichnung stimme ich der Antwort von @ Lundin zu, die den Standard für den Beweis zitiert , dass C11 die Annahme einer Beendigung für Endlosschleifen mit konstantem Ausdruck nicht zulässt, selbst wenn sie leer sind (keine E / A, flüchtig, Synchronisation oder andere) sichtbare Nebenwirkungen).
Dies ist der Satz von Bedingungen, unter denen eine Schleife zu einer leeren ASM-Schleife für eine normale CPU kompiliert werden kann. (Selbst wenn der Body in der Quelle nicht leer war, können Zuweisungen zu Variablen für andere Threads oder Signalhandler ohne Data-Race-UB nicht sichtbar sein, während die Schleife ausgeführt wird. Eine konforme Implementierung könnte solche Schleifenkörper also entfernen, wenn dies gewünscht wird Dann bleibt die Frage, ob die Schleife selbst entfernt werden kann. ISO C11 sagt ausdrücklich nein.)
Angesichts der Tatsache, dass C11 diesen Fall als einen Fall auszeichnet, bei dem die Implementierung nicht davon ausgehen kann, dass die Schleife endet (und dass es sich nicht um UB handelt), scheint es klar zu sein, dass die Schleife zur Laufzeit vorhanden sein soll. Eine Implementierung, die auf CPUs mit einem Ausführungsmodell abzielt, das nicht unendlich viel Arbeit in endlicher Zeit erledigen kann, hat keine Rechtfertigung für das Entfernen einer leeren konstanten Endlosschleife. Oder sogar im Allgemeinen geht es bei der genauen Formulierung darum, ob davon ausgegangen werden kann, dass sie "enden" oder nicht. Wenn eine Schleife nicht beendet werden kann, bedeutet dies, dass späterer Code nicht erreichbar ist, unabhängig davon, welche Argumente Sie zu Mathematik und Unendlichkeiten vorbringen und wie lange es dauert, auf einer hypothetischen Maschine unendlich viel Arbeit zu leisten.
Darüber hinaus ist Clang nicht nur eine ISO C-konforme DeathStation 9000, sondern soll auch für die reale Low-Level-Systemprogrammierung, einschließlich Kernel und eingebetteter Inhalte, nützlich sein. Unabhängig davon, ob Sie Argumente über C11 akzeptieren, die das Entfernen von C11 ermöglichen
while(1);
, ist es nicht sinnvoll, dass Clang dies tatsächlich tun möchte. Wenn Sie schreibenwhile(1);
, war das wahrscheinlich kein Unfall. Das Entfernen von Schleifen, die versehentlich unendlich werden (mit Laufzeitvariablen-Steuerausdrücken), kann nützlich sein, und es ist für Compiler sinnvoll, dies zu tun.Es ist selten, dass Sie nur bis zum nächsten Interrupt drehen möchten, aber wenn Sie das in C schreiben, ist das definitiv das, was Sie erwarten. (Und was ist geschehen in GCC und Clang, mit Ausnahme von Clang , wenn die Endlos - Schleife in einer Wrapper - Funktion ist).
Wenn der Scheduler in einem primitiven Betriebssystemkernel beispielsweise keine auszuführenden Aufgaben hat, wird möglicherweise die inaktive Aufgabe ausgeführt. Eine erste Implementierung davon könnte sein
while(1);
.Oder für Hardware ohne stromsparende Leerlauffunktion ist dies möglicherweise die einzige Implementierung. (Bis in die frühen 2000er Jahre war das auf x86 meiner Meinung nach nicht selten. Obwohl der
hlt
Befehl vorhanden war, konnte IDK eine bedeutende Menge Strom sparen, bis CPUs anfingen, Leerlaufzustände mit geringem Stromverbrauch zu haben.)quelle
-ffreestanding -fno-strict-aliasing
. Es funktioniert gut mit ARM und vielleicht mit älteren AVR.Nur fürs Protokoll, Clang benimmt sich auch schlecht mit
goto
:Es erzeugt die gleiche Ausgabe wie in der Frage, dh:
Ich sehe keine Möglichkeit, dies so zu lesen, wie es in C11 erlaubt ist, wo nur steht:
Wie
goto
kein „Wiederholungsanweisung“ (6.8.5 Listenwhile
,do
undfor
) nichts über die speziellen „Beendigungs angenommen“ Ablässe anwenden, wie Sie wollen , sie zu lesen.Gemäß der ursprünglichen Frage ist der Godbolt-Link-Compiler x86-64 Clang 9.0.0 und Flags sind
-g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0 -fcolor-diagnostics -fno-crash-diagnostics -O2 -std=c11 example.c
Mit anderen wie x86-64 GCC 9.2 erhalten Sie das ziemlich gut perfekt:
Flaggen:
-g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c
quelle
nasty: goto nasty
dass es konform sein kann und die CPU (s) nicht drehen kann, bis Benutzer oder Ressourcen erschöpft sind.bar()
innenfoo()
als Aufruf von__1foo
bis__2bar
, von__2foo
bis verarbeiten__3bar
. usw. und von__16foo
bis__launch_nasal_demons
, wodurch dann alle automatischen Objekte statisch zugeordnet werden können und das, was normalerweise als "Laufzeit" -Limit gilt, in ein Übersetzungslimit umgewandelt wird.Ich werde den Anwalt des Teufels spielen und argumentieren, dass der Standard einem Compiler nicht ausdrücklich verbietet, eine Endlosschleife zu optimieren.
Lassen Sie uns das analysieren. Es kann davon ausgegangen werden, dass eine Iterationsanweisung, die bestimmte Kriterien erfüllt, endet:
Dies sagt nichts darüber aus, was passiert, wenn die Kriterien nicht erfüllt sind und die Annahme, dass eine Schleife auch dann enden kann, nicht ausdrücklich verboten ist, solange andere Regeln des Standards eingehalten werden.
do { } while(0)
oderwhile(0){}
sind immerhin Iterationsanweisungen (Schleifen), die die Kriterien nicht erfüllen, die es einem Compiler ermöglichen, nur aus einer Laune heraus anzunehmen, dass sie beendet werden und dennoch offensichtlich enden.Aber kann der Compiler einfach optimieren
while(1){}
?5.1.2.3p4 sagt:
Dies erwähnt Ausdrücke, keine Aussagen, ist also nicht 100% überzeugend, erlaubt aber sicherlich Anrufe wie:
übersprungen werden. Interessanterweise überspringt Clang es und Gcc nicht .
quelle
while(1){}
eine unendliche Folge von1
Bewertungen mit Bewertungen verflochten{}
, aber wo im Standard steht, dass diese Bewertungen Zeit ungleich Null benötigen ? Das gcc-Verhalten ist nützlicher, da Sie keine Tricks mit Speicherzugriff oder Tricks außerhalb der Sprache benötigen. Aber ich bin nicht davon überzeugt, dass der Standard diese Optimierung in Clang verbietet. Wenn es nichtwhile(1){}
beabsichtigt ist , nicht optimierbar zu machen, sollte der Standard explizit darüber sein, und Endlosschleifen sollten in 5.1.2.3p2 als beobachtbarer Nebeneffekt aufgeführt werden.1
Bedingung als Wertberechnung behandeln. Die Ausführungszeit spielt keine Rolle - was zählt, ist, waswhile(A){} B;
möglicherweise nicht vollständig wegoptimiert, nicht optimiertB;
und nicht neu sequenziert wirdB; while(A){}
. Um es mit der C11 abstrakter Maschine, Hervorhebung von dir: „impliziert das Vorhandensein eines Sequenzpunktes zwischen der Auswertung von Ausdrücken A und B , dass jede Wertberechnung und Nebenwirkung mit A zugeordnet vor jeder Wertberechnung sequenziert und Nebenwirkung im Zusammenhang mit B .“ Der Wert vonA
wird eindeutig verwendet (von der Schleife).Ich war überzeugt, dass dies nur ein alter Fehler ist. Ich lasse meine Tests unten und insbesondere den Verweis auf die Diskussion im Standardausschuss für einige Gründe, die ich zuvor hatte.
Ich denke, dies ist undefiniertes Verhalten (siehe Ende), und Clang hat nur eine Implementierung. GCC funktioniert in der Tat wie erwartet und optimiert nur die
unreachable
print-Anweisung, verlässt jedoch die Schleife. Einige, wie Clang seltsamerweise Entscheidungen trifft, wenn er das In-Lining kombiniert und bestimmt, was es mit der Schleife tun kann.Das Verhalten ist besonders seltsam - es entfernt den endgültigen Druck, so dass die Endlosschleife "gesehen" wird, aber dann auch die Schleife entfernt wird.
Soweit ich das beurteilen kann, ist es noch schlimmer. Entfernen der Inline erhalten wir:
So wird die Funktion erstellt und der Aufruf optimiert. Dies ist noch widerstandsfähiger als erwartet:
führt zu einer sehr nicht optimalen Assemblierung für die Funktion, aber der Funktionsaufruf wird wieder optimiert! Noch schlimmer:
Ich habe eine Reihe anderer Tests durchgeführt, bei denen eine lokale Variable hinzugefügt und vergrößert, ein Zeiger übergeben, ein
goto
usw. verwendet wurde. An diesem Punkt würde ich aufgeben. Wenn Sie clang verwenden müssenmacht den Job. Es saugt (offensichtlich) an der Optimierung und verlässt das redundante Finale
printf
. Zumindest stoppt das Programm nicht. Vielleicht doch GCC?Nachtrag
Nach der Diskussion mit David gebe ich zu, dass der Standard nicht sagt: "Wenn die Bedingung konstant ist, können Sie nicht davon ausgehen, dass die Schleife endet." Als solches und unter dem Standard gewährt, gibt es kein beobachtbares Verhalten (wie im Standard definiert), würde ich nur für Konsistenz argumentieren - wenn ein Compiler eine Schleife optimiert, weil er davon ausgeht, dass sie beendet wird, sollte er die folgenden Anweisungen nicht optimieren.
Heck n1528 hat diese als undefiniertes Verhalten, wenn ich das richtig lese. Speziell
Von hier aus kann es meiner Meinung nach nur zu einer Diskussion darüber kommen, was wir wollen (erwartet?) Und nicht was erlaubt ist.
quelle
Es scheint, dass dies ein Fehler im Clang-Compiler ist. Wenn die
die()
Funktion nicht gezwungen ist , eine statische Funktion zu sein, beseitigen Sie siestatic
und machen Sie sieinline
:Es funktioniert wie erwartet, wenn es mit dem Clang-Compiler kompiliert wird, und ist auch portabel.
Compiler Explorer (godbolt.org) - clang 9.0.0
-O3 -std=c11 -pedantic-errors
quelle
static inline
?Folgendes scheint für mich zu funktionieren:
bei godbolt
Wenn Clang explizit angewiesen wird, diese eine Funktion nicht zu optimieren, wird erwartungsgemäß eine Endlosschleife ausgegeben. Hoffentlich gibt es eine Möglichkeit, bestimmte Optimierungen selektiv zu deaktivieren, anstatt sie alle so auszuschalten. Clang weigert sich jedoch immer noch, Code für die Sekunde
printf
auszugeben. Um dies zu erzwingen, musste ich den Code weiter modifizieren,main
um:Es sieht so aus, als müssten Sie Optimierungen für Ihre Endlosschleifenfunktion deaktivieren und dann sicherstellen, dass Ihre Endlosschleife bedingt aufgerufen wird. In der realen Welt ist letzteres sowieso fast immer der Fall.
quelle
printf
Schleife generiert wird, wenn die Schleife tatsächlich für immer läuft, da in diesem Fall die zweiteprintf
wirklich nicht erreichbar ist und daher gelöscht werden kann. (Clangs Fehler besteht darin, sowohl die Nichterreichbarkeit zu erkennen als auch die Schleife so zu löschen, dass der nicht erreichbare Code erreicht wird.)__attribute__ ((optimize(1)))
, aber Clang ignoriert sie als nicht unterstützt: godbolt.org/z/4ba2HM . gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.htmlEine konforme Implementierung kann und viele praktische Implementierungen willkürliche Grenzen für die Ausführungsdauer eines Programms oder die Anzahl der auszuführenden Anweisungen festlegen und sich willkürlich verhalten, wenn diese Grenzen verletzt werden oder - nach der "Als-ob" -Regel --wenn es feststellt, dass sie unweigerlich verletzt werden. Vorausgesetzt, eine Implementierung kann mindestens ein Programm erfolgreich verarbeiten, das nominell alle in N1570 5.2.4.1 aufgeführten Grenzwerte ausübt, ohne Übersetzungsgrenzwerte zu erreichen, das Vorhandensein von Grenzwerten, den Umfang ihrer Dokumentation und die Auswirkungen einer Überschreitung Alle Fragen der Implementierungsqualität, die außerhalb der Zuständigkeit des Standards liegen.
Ich denke, die Absicht des Standards ist ganz klar, dass Compiler nicht davon ausgehen sollten, dass eine
while(1) {}
Schleife ohne Nebenwirkungen oderbreak
Anweisungen endet. Entgegen der Meinung einiger Leute luden die Autoren des Standards Compiler-Autoren nicht ein, dumm oder stumpf zu sein. Eine konforme Implementierung könnte nützlich sein, um zu entscheiden, ein Programm zu beenden, das, wenn es nicht unterbrochen wird, mehr nebenwirkungsfreie Anweisungen ausführt, als es Atome im Universum gibt, aber eine Qualitätsimplementierung sollte eine solche Aktion nicht auf der Grundlage einer Annahme über ausführen Kündigung, sondern auf der Grundlage, dass dies nützlich sein könnte und nicht (im Gegensatz zu Clangs Verhalten) schlimmer als nutzlos wäre.quelle
Die Schleife hat keine Nebenwirkungen und kann daher optimiert werden. Die Schleife ist effektiv eine unendliche Anzahl von Iterationen von null Arbeitseinheiten. Dies ist in der Mathematik und in der Logik undefiniert, und der Standard sagt nicht aus, ob eine Implementierung eine unendliche Anzahl von Dingen ausführen darf, wenn jedes in null Zeit erledigt werden kann. Clangs Interpretation ist durchaus vernünftig, wenn Unendlichkeit mal Null als Null und nicht als Unendlichkeit behandelt wird. Der Standard sagt nicht aus, ob eine Endlosschleife enden kann oder nicht, wenn alle Arbeiten in den Schleifen tatsächlich abgeschlossen sind.
Der Compiler darf alles optimieren, was im Standard nicht beobachtbar ist. Das beinhaltet die Ausführungszeit. Es ist nicht erforderlich, die Tatsache beizubehalten, dass die Schleife, wenn sie nicht optimiert wird, unendlich viel Zeit in Anspruch nehmen würde. Es ist erlaubt, dies auf eine viel kürzere Laufzeit zu ändern - tatsächlich ist dies der Punkt der meisten Optimierungen. Ihre Schleife wurde optimiert.
Selbst wenn clang den Code naiv übersetzt, können Sie sich eine optimierende CPU vorstellen, die jede Iteration in der Hälfte der Zeit abschließen kann, die die vorherige Iteration benötigt hat. Das würde die Endlosschleife buchstäblich in einer endlichen Zeitspanne vervollständigen. Verstößt eine solche optimierende CPU gegen den Standard? Es scheint ziemlich absurd zu sagen, dass eine optimierende CPU gegen den Standard verstoßen würde, wenn sie zu gut für die Optimierung ist. Gleiches gilt für einen Compiler.
quelle
Es tut mir leid, wenn dies absurderweise nicht der Fall ist. Ich bin auf diesen Beitrag gestoßen und weiß aufgrund meiner jahrelangen Verwendung der Gentoo Linux-Distribution, dass Sie -O0 (Zero) verwenden sollten, wenn der Compiler Ihren Code nicht optimieren soll. Ich war neugierig darauf und habe den obigen Code kompiliert und ausgeführt, und die Schleife läuft unbegrenzt. Kompiliert mit clang-9:
quelle
Eine leere
while
Schleife hat keine Nebenwirkungen auf das System.Deshalb entfernt Clang es. Es gibt "bessere" Wege, um das beabsichtigte Verhalten zu erreichen, die Sie dazu zwingen, Ihre Absichten offensichtlicher zu machen.
while(1);
ist baaadd.quelle
abort()
oderexit()
. Wenn eine Funktion auftritt, in der eine Funktion feststellt, dass eine fortgesetzte Ausführung (möglicherweise aufgrund einer Speicherbeschädigung) schlimmer als gefährlich ist, besteht ein häufiges Standardverhalten für eingebettete Bibliotheken darin, eine Funktion aufzurufen, die a ausführtwhile(1);
. Es kann für den Compiler nützlich sein, Optionen zu haben um ein nützlicheres Verhalten zu ersetzen , aber jeder Compiler-Writer, der nicht herausfinden kann, wie ein so einfaches Konstrukt als Hindernis für die weitere Programmausführung behandelt werden kann, kann nicht mit komplexen Optimierungen vertraut werden.