Wie erstelle ich eine Endlosschleife, die nicht optimiert werden kann?

131

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 ud2nachgestellte 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 volatileVariable 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, begingefolgt von unreachableder Endlosschleife.

Wie bringt man Clang dazu, eine ordnungsgemäße Endlosschleife ohne Speicherzugriff mit aktivierten Optimierungen auszugeben?

nneonneo
quelle
3
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Bhargav Rao
2
Es gibt keine tragbare Lösung, die keine Nebenwirkungen hat. Wenn Sie keinen Speicherzugriff wünschen, besteht Ihre beste Hoffnung darin, ein flüchtiges Zeichen ohne Vorzeichen zu registrieren. aber Register geht in C ++ 17 weg.
Scott M
25
Vielleicht liegt das nicht im Rahmen der Frage, aber ich bin gespannt, warum Sie das tun möchten. Sicher gibt es einen anderen Weg, um Ihre eigentliche Aufgabe zu erfüllen. Oder ist das nur akademischer Natur?
Cruncher
1
@Cruncher: Die Auswirkungen eines bestimmten Versuchs, ein Programm auszuführen, können nützlich, im Wesentlichen nutzlos oder wesentlich schlechter als nutzlos sein. Eine Ausführung, die dazu führt, dass ein Programm in einer Endlosschleife stecken bleibt, ist möglicherweise nutzlos, aber dennoch anderen Verhaltensweisen vorzuziehen, die ein Compiler möglicherweise ersetzt.
Superkatze
6
@Cruncher: Weil der Code möglicherweise in einem freistehenden Kontext ausgeführt wird, in dem es kein Konzept gibt 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.
Supercat

Antworten:

77

Der C11-Standard sagt dies, 6.8.5 / 6:

Eine Iterationsanweisung, deren steuernder Ausdruck kein konstanter Ausdruck ist ( 156) , die keine Eingabe- / Ausgabeoperationen ausführt, nicht auf flüchtige Objekte zugreift und keine Synchronisations- oder Atomoperationen in ihrem Körper ausführt, den Ausdruck steuert oder (im Fall eines for) Anweisung) sein Ausdruck-3 kann von der Implementierung als beendet angenommen werden. 157)

Die beiden Fußnoten sind nicht normativ, bieten jedoch nützliche Informationen:

156) Ein ausgelassener steuernder Ausdruck wird durch eine Konstante ungleich Null ersetzt, die ein konstanter Ausdruck ist.

157) Dies soll Compiler-Transformationen wie das Entfernen leerer Schleifen ermöglichen, auch wenn die Beendigung nicht nachgewiesen werden kann.

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

.LC0:
        .string "begin"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
.L2:
        jmp     .L2

clang 9.0.0 -O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.Lstr:
        .asciz  "begin"

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:

#include <stdio.h>
#include <setjmp.h>

static _Noreturn void die() {
    while(1)
        ;
}

int main(void) {
    jmp_buf buf;
    _Bool first = !setjmp(buf);

    printf("begin\n");
    if(first)
    {
      die();
      longjmp(buf, 1);
    }
    printf("unreachable\n");
}

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.

setjmpwird bei der ersten Ausführung 0 zurückgeben, daher sollte dieses Programm nur in die while(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.).

Lundin
quelle
15
Ich bin nicht einverstanden mit "dies ist ein kristallklarer konstanter Ausdruck, so dass die Implementierung möglicherweise nicht davon ausgeht, dass er beendet wird" . Dies führt wirklich zu einer wählerischen Sprache, aber 6.8.5/6in 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 ...
Kabanus
7
@kabanus Der zitierte Teil ist ein Sonderfall. Wenn nicht (Sonderfall), bewerten und sequenzieren Sie den Code wie gewohnt. Wenn Sie dasselbe Kapitel weiterlesen, wird der steuernde Ausdruck mit Ausnahme des angegebenen Sonderfalls wie für jede Iterationsanweisung angegeben ("wie durch die Semantik angegeben") ausgewertet. Es folgt den gleichen Regeln wie die Bewertung einer Wertberechnung, die sequenziert und genau definiert ist.
Lundin
2
Ich stimme zu, aber Sie wären nicht überrascht, dass int z=3; int y=2; int x=1; printf("%d %d\n", x, z);es 2in der Baugruppe keine gibt , so dass im leeren sinnlosen Sinne xnicht nach, ysondern nach zder 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).
Kabanus
2
@MSalters Einer meiner Kommentare wurde gelöscht, aber danke für die Eingabe - und ich stimme zu. In meinem Kommentar heißt es, ich denke, dies ist das Herzstück der Debatte - es ist while(1);dasselbe wie eine int 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.
Kabanus
2
"Eine solche Implementierung wäre hoffnungslos kaputt, da 'für immer' Schleifen ein gängiges Programmierkonstrukt sind." - Ich verstehe das Gefühl, aber das Argument ist fehlerhaft, weil es identisch auf C ++ angewendet werden könnte, aber ein C ++ - Compiler, der diese Schleife optimiert hat, wäre nicht kaputt, sondern konform.
Konrad Rudolph
52

Sie müssen einen Ausdruck einfügen, der eine Nebenwirkung verursachen kann.

Die einfachste Lösung:

static void die() {
    while(1)
       __asm("");
}

Godbolt Link

P__J__
quelle
21
Erklärt jedoch nicht, warum Clang sich verhält.
Lundin
4
Es reicht jedoch aus, nur zu sagen, "es ist ein Fehler im Klirren". Ich möchte hier allerdings zuerst ein paar Dinge ausprobieren, bevor ich "Bug" schreie.
Lundin
3
@Lundin Ich weiß nicht, ob es ein Fehler ist. Standard ist in diesem Fall technisch nicht genau
P__J__
4
Zum Glück ist GCC Open Source und ich kann einen Compiler schreiben, der Ihr Beispiel optimiert. Und ich könnte dies für jedes Beispiel tun, das Sie sich jetzt und in Zukunft einfallen lassen.
Thomas Weller
3
@ThomasWeller: GCC-Entwickler würden keinen Patch akzeptieren, der diese Schleife optimiert. es würde dokumentiertes = garantiertes Verhalten verletzen. Siehe meinen vorherigen Kommentar: asm("")ist implizit asm 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 wie asm("mfence")oder sicher cli.)
Peter Cordes
50

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.sideeffectOpcodes in solche Schleifen einfügt. Dies ist, was Rust plant, also wird Clang (beim Kompilieren von C-Code) wahrscheinlich auch das tun müssen.

Arnavion
quelle
5
Nichts wie der Geruch eines Fehlers, der älter als ein Jahrzehnt ist ... mit mehreren vorgeschlagenen Korrekturen und Patches ... wurde jedoch noch nicht behoben.
Ian Kemp
4
@IanKemp: Damit sie den Fehler jetzt beheben können, müssen sie bestätigen, dass sie zehn Jahre gebraucht haben, um den Fehler zu beheben. Besser hoffen, dass sich der Standard ändert, um ihr Verhalten zu rechtfertigen. Selbst wenn sich der Standard ändern würde, würde dies ihr Verhalten natürlich nicht rechtfertigen, außer in den Augen von Menschen, die die Änderung des Standards als Hinweis darauf betrachten würden, dass das frühere Verhaltensmandat des Standards ein Fehler war, der rückwirkend korrigiert werden sollte.
Supercat
4
Es wurde in dem Sinne "behoben", dass LLVM die Operation hinzugefügt hat 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 aufeinanderfolgender sideeffectOperationen 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.
Arnavion
@Arnavion: Gibt es eine Möglichkeit, anzuzeigen, dass Vorgänge möglicherweise verzögert werden, wenn oder bis die Ergebnisse verwendet werden. Wenn jedoch Daten dazu führen würden, dass ein Programm endlos wiederholt wird, würde der Versuch, über Datenabhängigkeiten hinweg fortzufahren, das Programm schlimmer als nutzlos machen ? Das Hinzufügen falscher Nebenwirkungen, die die früheren nützlichen Optimierungen verhindern würden, um zu verhindern, dass der Optimierer ein Programm schlechter als nutzlos macht, klingt nicht nach einem Rezept für Effizienz.
Superkatze
Diese Diskussion gehört wahrscheinlich auf die LLVM / Clang-Mailinglisten. FWIW das LLVM-Commit, das die Operation hinzugefügt hat, hat auch mehrere Optimierungsdurchläufe darüber gelehrt. Außerdem experimentierte Rust mit dem Einfügen von sideeffectOps 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.
Arnavion
32

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 die print;while(1);print;Funktion in ihren Aufrufer ( Godbolt ) eingebunden ist . -std=gnu11vs. -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 asmAnweisung 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.

#include <stdio.h>
int main() {
    printf("begin\n");
    while(1);
    //infloop_nonconst(1);
    //infloop();
    printf("unreachable\n");
}

Auf dem Godbolt-Compiler-Explorer wird Clang 9.0 -O3 als C ( -xc) für x86-64 kompiliert :

main:                                   # @main
        push    rax                       # re-align the stack by 16
        mov     edi, offset .Lstr         # non-PIE executable can use 32-bit absolute addresses
        call    puts
.LBB3_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB3_1                   # infinite loop


.section .rodata
 ...
.Lstr:
        .asciz  "begin"

Derselbe Compiler mit denselben Optionen kompiliert einen main, infloop() { while(1); }der zuerst denselben aufruft puts, dann aber mainnach 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

  • eine label: jmp labelEndlosschleife ausgeben
  • oder (wenn wir akzeptieren, dass die Endlosschleife entfernt werden kann) einen weiteren Aufruf ausgeben, um die 2. Zeichenfolge zu drucken, und dann return 0von main.

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öglichenwhile(1); , ist es nicht sinnvoll, dass Clang dies tatsächlich tun möchte. Wenn Sie schreiben while(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 hltBefehl vorhanden war, konnte IDK eine bedeutende Menge Strom sparen, bis CPUs anfingen, Leerlaufzustände mit geringem Stromverbrauch zu haben.)

Peter Cordes
quelle
1
Verwendet jemand aus Neugier tatsächlich Clang für eingebettete Systeme? Ich habe es noch nie gesehen und arbeite ausschließlich mit Embedded. gcc ist erst "vor kurzem" (vor 10 Jahren) in den Embedded-Markt eingetreten und ich benutze diesen skeptisch, vorzugsweise mit geringen Optimierungen und immer mit -ffreestanding -fno-strict-aliasing. Es funktioniert gut mit ARM und vielleicht mit älteren AVR.
Lundin
1
@Lundin: IDK über Embedded, aber ja, Leute bauen Kernel mit Clang, zumindest manchmal Linux. Vermutlich auch Darwin für MacOS.
Peter Cordes
2
bugs.llvm.org/show_bug.cgi?id=965 Dieser Fehler sieht relevant aus, aber ich bin nicht sicher, ob es das ist, was wir hier sehen.
bracco23
1
@lundin - Ich bin mir ziemlich sicher, dass wir GCC (und viele andere Toolkits) in den 90er Jahren für eingebettete Arbeiten verwendet haben, mit RTOS wie VxWorks und PSOS. Ich verstehe nicht, warum Sie sagen, dass GCC erst kürzlich in den Embedded-Markt eingetreten ist.
Jeff Learman
1
@ JeffLearman Wurde dann kürzlich Mainstream? Wie auch immer, das gcc-Fiasko mit striktem Aliasing ereignete sich erst nach der Einführung von C99, und neuere Versionen davon scheinen auch bei strengen Aliasing-Verstößen keine Bananen mehr zu sein. Trotzdem bleibe ich skeptisch, wenn ich es benutze. Was Clang betrifft, ist die neueste Version in Bezug auf ewige Schleifen offensichtlich völlig kaputt, sodass sie nicht für eingebettete Systeme verwendet werden kann.
Lundin
14

Nur fürs Protokoll, Clang benimmt sich auch schlecht mit goto:

static void die() {
nasty:
    goto nasty;
}

int main() {
    int x; printf("begin\n");
    die();
    printf("unreachable\n");
}

Es erzeugt die gleiche Ausgabe wie in der Frage, dh:

main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

Ich sehe keine Möglichkeit, dies so zu lesen, wie es in C11 erlaubt ist, wo nur steht:

6.8.6.1 (2) Eine gotoAnweisung bewirkt einen bedingungslosen Sprung zu der Anweisung, der in der umschließenden Funktion das benannte Label vorangestellt ist.

Wie gotokein „Wiederholungsanweisung“ (6.8.5 Listen while, dound for) 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:

.LC0:
  .string "begin"
main:
  sub rsp, 8
  mov edi, OFFSET FLAT:.LC0
  call puts
.L2:
  jmp .L2

Flaggen: -g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c

Jonathanjo
quelle
Eine konforme Implementierung kann ein undokumentiertes Übersetzungslimit für die Ausführungszeit oder CPU-Zyklen aufweisen, das bei Überschreitung oder bei unvermeidlichen Programmeingaben, die das Limit überschreiten, zu willkürlichem Verhalten führen kann. Solche Dinge sind ein Problem der Implementierungsqualität, das außerhalb der Zuständigkeit des Standards liegt. Es scheint seltsam, dass die Clang-Betreuer so hartnäckig auf ihrem Recht bestehen, eine Implementierung von schlechter Qualität zu produzieren, aber der Standard erlaubt dies.
Supercat
2
@supercat danke für den Kommentar ... warum sollte das Überschreiten eines Übersetzungslimits etwas anderes bewirken, als die Übersetzungsphase nicht zu bestehen und die Ausführung zu verweigern? Außerdem: " 5.1.1.3 Diagnose Eine konforme Implementierung muss ... eine Diagnosemeldung ... erzeugen, wenn eine vorverarbeitete Übersetzungseinheit oder Übersetzungseinheit eine Verletzung einer Syntaxregel oder -beschränkung enthält ...". Ich kann nicht sehen, wie sich fehlerhaftes Verhalten in der Ausführungsphase jemals anpassen kann.
Jonathanjo
Der Standard wäre völlig unmöglich zu implementieren, wenn alle Implementierungsgrenzen zum Zeitpunkt der Erstellung aufgelöst werden müssten, da man ein streng konformes Programm schreiben könnte, das mehr Stapelbytes erfordern würde, als Atome im Universum vorhanden sind. Es ist unklar, ob Laufzeitbeschränkungen mit "Übersetzungsbeschränkungen" verwechselt werden sollten, aber eine solche Konzession ist eindeutig erforderlich, und es gibt keine andere Kategorie, in die sie eingeordnet werden könnte.
Supercat
1
Ich habe auf Ihren Kommentar zu "Übersetzungslimits" geantwortet. Natürlich gibt es auch Ausführungsbeschränkungen. Ich gebe zu, ich verstehe nicht, warum Sie vorschlagen, sie mit Übersetzungsbeschränkungen zu verwechseln, oder warum Sie sagen, dass dies notwendig ist. Ich sehe nur keinen Grund zu sagen, nasty: goto nastydass es konform sein kann und die CPU (s) nicht drehen kann, bis Benutzer oder Ressourcen erschöpft sind.
Jonathanjo
1
Der Standard enthält keinen Hinweis auf "Ausführungsgrenzen", die ich finden konnte. Dinge wie das Verschachteln von Funktionsaufrufen werden normalerweise durch die Stapelzuweisung behandelt, aber eine konforme Implementierung, die Funktionsaufrufe auf eine Tiefe von 16 begrenzt, kann 16 Kopien jeder Funktion erstellen und einen Aufruf von bar()innen foo()als Aufruf von __1foobis __2bar, von __2foobis verarbeiten __3bar. usw. und von __16foobis __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.
Supercat
5

Ich werde den Anwalt des Teufels spielen und argumentieren, dass der Standard einem Compiler nicht ausdrücklich verbietet, eine Endlosschleife zu optimieren.

Eine Iterationsanweisung, deren steuernder Ausdruck kein konstanter Ausdruck ist (156), die keine Eingabe- / Ausgabeoperationen ausführt, nicht auf flüchtige Objekte zugreift und keine Synchronisations- oder Atomoperationen in ihrem Körper ausführt, den Ausdruck steuert oder (im Fall eines for) Anweisung) sein Ausdruck-3 kann von der Implementierung als beendet angenommen werden.157)

Lassen Sie uns das analysieren. Es kann davon ausgegangen werden, dass eine Iterationsanweisung, die bestimmte Kriterien erfüllt, endet:

if (satisfiesCriteriaForTerminatingEh(a_loop)) 
    if (whatever_reason_or_just_because_you_feel_like_it)
         assumeTerminates(a_loop);

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)oder while(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:

In der abstrakten Maschine werden alle Ausdrücke gemäß der Semantik ausgewertet. Eine tatsächliche Implementierung muss keinen Teil eines Ausdrucks auswerten, wenn daraus geschlossen werden kann, dass sein Wert nicht verwendet wird und keine erforderlichen Nebenwirkungen auftreten (einschließlich solcher, die durch den Aufruf einer Funktion oder den Zugriff auf ein flüchtiges Objekt verursacht werden).

Dies erwähnt Ausdrücke, keine Aussagen, ist also nicht 100% überzeugend, erlaubt aber sicherlich Anrufe wie:

void loop(void){ loop(); }

int main()
{
    loop();
}

übersprungen werden. Interessanterweise überspringt Clang es und Gcc nicht .

PSkocik
quelle
"Dies sagt nichts darüber aus, was passiert, wenn die Kriterien nicht erfüllt sind." 6.8.5.1 Die while-Anweisung: "Die Auswertung des steuernden Ausdrucks erfolgt vor jeder Ausführung des Schleifenkörpers." Das ist es. Dies ist eine Wertberechnung (eines konstanten Ausdrucks), die unter die Regel der abstrakten Maschine 5.1.2.3 fällt, die den Begriff Bewertung definiert: "Die Bewertung eines Ausdrucks umfasst im Allgemeinen sowohl Wertberechnungen als auch die Einleitung von Nebenwirkungen." Und gemäß demselben Kapitel werden alle diese Auswertungen gemäß der Semantik sequenziert und ausgewertet.
Lundin
1
@Lundin Ist also while(1){}eine unendliche Folge von 1Bewertungen 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 nicht while(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.
PSkocik
1
Ich denke, es ist angegeben, wenn Sie die 1Bedingung als Wertberechnung behandeln. Die Ausführungszeit spielt keine Rolle - was zählt, ist, was while(A){} B;möglicherweise nicht vollständig wegoptimiert, nicht optimiert B;und nicht neu sequenziert wird B; 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 von Awird eindeutig verwendet (von der Schleife).
Lundin
2
+1 Auch wenn es mir so vorkommt, als ob "Ausführung auf unbestimmte Zeit ohne Ausgabe hängt" ein "Nebeneffekt" in jeder Definition von "Nebeneffekt" ist, der Sinn macht und über den Standard in einem Vakuum hinaus nützlich ist, hilft dies zu erklären die Denkweise, aus der es für jemanden Sinn machen kann.
mtraceur
1
Nahezu "Optimieren einer Endlosschleife" : Es ist nicht ganz klar, ob sich "es" auf den Standard oder den Compiler bezieht - vielleicht umformulieren? Angesichts "obwohl es wahrscheinlich sollte" und nicht "obwohl es wahrscheinlich nicht sollte" ist es wahrscheinlich der Standard, auf den sich "es" bezieht.
Peter Mortensen
2

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 unreachableprint-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:

die: # @die
.LBB0_1: # =>This Inner Loop Header: Depth=1
  jmp .LBB0_1
main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

So wird die Funktion erstellt und der Aufruf optimiert. Dies ist noch widerstandsfähiger als erwartet:

#include <stdio.h>

void die(int x) {
    while(x);
}

int main() {
    printf("begin\n");
    die(1);
    printf("unreachable\n");
}

führt zu einer sehr nicht optimalen Assemblierung für die Funktion, aber der Funktionsaufruf wird wieder optimiert! Noch schlimmer:

void die(x) {
    while(x++);
}

int main() {
    printf("begin\n");
    die(1);
    printf("unreachable\n");
}

Ich habe eine Reihe anderer Tests durchgeführt, bei denen eine lokale Variable hinzugefügt und vergrößert, ein Zeiger übergeben, ein gotousw. verwendet wurde. An diesem Punkt würde ich aufgeben. Wenn Sie clang verwenden müssen

static void die() {
    int volatile x = 1;
    while(x);
}

macht 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

Ein Hauptproblem dabei ist, dass Code sich über eine möglicherweise nicht terminierende Schleife bewegen kann

Von hier aus kann es meiner Meinung nach nur zu einer Diskussion darüber kommen, was wir wollen (erwartet?) Und nicht was erlaubt ist.

Kabanus
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Bhargav Rao
Zu "plain all bug" : Meinst du " plain old bug" ?
Peter Mortensen
@PeterMortensen "ole" wäre auch für mich in Ordnung.
Kabanus
2

Es scheint, dass dies ein Fehler im Clang-Compiler ist. Wenn die die()Funktion nicht gezwungen ist , eine statische Funktion zu sein, beseitigen Sie sie staticund machen Sie sie inline:

#include <stdio.h>

inline void die(void) {
    while(1)
        ;
}

int main(void) {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

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

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB0_1
.Lstr:
        .asciz  "begin"
HS
quelle
Was ist mit static inline?
SS Anne
1

Folgendes scheint für mich zu funktionieren:

#include <stdio.h>

__attribute__ ((optnone))
static void die(void) {
    while (1) ;
}

int main(void) {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

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 printfauszugeben. Um dies zu erzwingen, musste ich den Code weiter modifizieren, mainum:

volatile int x = 0;
if (x == 0)
    die();

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.

bta
quelle
1
Es ist nicht erforderlich, dass die zweite printfSchleife generiert wird, wenn die Schleife tatsächlich für immer läuft, da in diesem Fall die zweite printfwirklich 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.)
Nneonneo
GCC-Dokumente __attribute__ ((optimize(1))), aber Clang ignoriert sie als nicht unterstützt: godbolt.org/z/4ba2HM . gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
Peter Cordes
0

Eine 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 oder breakAnweisungen 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.

Superkatze
quelle
-2

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.

David Schwartz
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew
4
Nach Ihrer Erfahrung (aus Ihrem Profil) kann ich nur den Schluss ziehen, dass dieser Beitrag in böser Absicht verfasst wurde, um den Compiler zu verteidigen. Sie argumentieren ernsthaft, dass etwas, das unendlich viel Zeit in Anspruch nimmt, so optimiert werden kann, dass es in der Hälfte der Zeit ausgeführt wird. Das ist auf jeder Ebene lächerlich und das weißt du.
Pipe
@pipe: Ich denke, die Betreuer von clang und gcc hoffen, dass eine zukünftige Version des Standards das Verhalten ihrer Compiler zulässt, und die Betreuer dieser Compiler können so tun, als wäre eine solche Änderung lediglich eine Korrektur eines langjährigen Fehlers im Standard. So haben sie beispielsweise die Common Initial Sequence-Garantien von C89 behandelt.
Supercat
@SSAnne: Hmm ... Ich denke nicht, dass dies ausreicht, um einige der unsoliden Schlussfolgerungen zu blockieren, die gcc und clang aus den Ergebnissen von Zeiger-Gleichheits-Vergleichen ziehen.
Supercat
@supercat Es gibt <s> andere </ s> Tonnen.
SS Anne
-2

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:

cc -O0 -std=c11 test.c -o test
Fellipe Weno
quelle
1
Es geht darum, eine Endlosschleife mit aktivierten Optimierungen zu erstellen.
SS Anne
-4

Eine leere whileSchleife 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.

Berühmte Jameis
quelle
6
In vielen eingebetteten Konstrukten gibt es kein Konzept von abort()oder exit(). 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ührt while(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.
Supercat
Gibt es eine Möglichkeit, Ihre Absichten deutlicher zu formulieren? Der Optimierer dient dazu, Ihr Programm zu optimieren, und das Entfernen redundanter Schleifen, die nichts bewirken, ist eine Optimierung. Dies ist wirklich ein philosophischer Unterschied zwischen dem abstrakten Denken der mathematischen Welt und der eher angewandten Ingenieurwelt.
Berühmte Jameis
Die meisten Programme verfügen über eine Reihe nützlicher Aktionen, die sie nach Möglichkeit ausführen sollten, und über eine Reihe von Aktionen, die schlimmer als nutzlos sind und die sie unter keinen Umständen ausführen dürfen. Viele Programme weisen in einem bestimmten Fall eine Reihe akzeptabler Verhaltensweisen auf, von denen eine, wenn die Ausführungszeit nicht beobachtbar ist, immer darin besteht, "willkürlich zu warten und dann eine Aktion aus der Menge auszuführen". Wenn alle Aktionen außer Warten in der Menge der Aktionen enthalten sind, die schlimmer als nutzlos sind, gibt es keine Anzahl von Sekunden N, für die sich "ewig warten"
merklich
... "N + 1 Sekunden warten und dann eine andere Aktion ausführen", sodass die Tatsache, dass der Satz tolerierbarer Aktionen außer Warten leer ist, nicht erkennbar ist. Wenn andererseits ein Code eine unerträgliche Aktion aus dem Satz möglicher Aktionen entfernt und eine dieser Aktionen ausgeführt wird trotzdem ausgeführt wird, sollte dies als beobachtbar angesehen werden. Leider verwenden C- und C ++ - Sprachregeln das Wort "annehmen" auf seltsame Weise, anders als jedes andere Feld der Logik oder des menschlichen Strebens, das ich identifizieren kann.
Superkatze
1
@FamousJameis ok, aber Clang entfernt nicht nur die Schleife - es analysiert statisch alles danach als nicht erreichbar und gibt eine ungültige Anweisung aus. Das ist nicht das, was Sie erwarten, wenn es nur die Schleife "entfernt".
Nneonneo