Ist während (1); undefiniertes Verhalten in C?

Lesezeit: 8 Minuten

Benutzer-Avatar
Toni der Löwe

In C++11 ist es Undefiniertes Verhalten, aber in C ist das der Fall while(1); ist undefiniertes Verhalten?

  • ich denke wenn for(;;) -Anweisung ist in C wohldefiniert dann while(1) sollte in C nicht undefiniert sein. Denken Sie daran, dass die Erkennung einer Endlosschleife ein unentscheidbares Problem ist.

    – Grijesh Chauhan

    8. Mai 2013 um 10:10 Uhr


  • Wenn Sie möchten, könnte ich etwas mehr auf 6.8.5 und 6 eingehen und insbesondere, warum es sehr unwahrscheinlich ist, dass die Compiler-Firma, für die ich arbeite, von dieser Klausel Gebrauch macht.

    – Bryan Olivier

    8. Mai 2013 um 10:46 Uhr


  • @BryanOlivier mach es 🙂

    – Toni der Löwe

    8. Mai 2013 um 10:52 Uhr

  • @ Tony, danke, es ist immer schön, ein Steckenpferd zu reiten.

    – Bryan Olivier

    8. Mai 2013 um 11:27 Uhr

  • Mögliches Duplikat von Ist eine (leere) Endlosschleife undefiniertes Verhalten in C?

    – Jinawee

    16. Januar 2019 um 17:36 Uhr

Benutzer-Avatar
Bryan Olivier

Es ist ein wohldefiniertes Verhalten. In C11 wurde ein neuer Abschnitt 6.8.5 ad 6 hinzugefügt

Eine Iterationsanweisung, deren steuernder Ausdruck kein konstanter Ausdruck ist,156) die keine Ein-/Ausgabeoperationen durchführt, nicht auf flüchtige Objekte zugreift und keine Synchronisation oder atomare Operationen in ihrem Hauptteil durchführt, den Ausdruck oder (im Fall einer for-Anweisung) ihren Ausdruck-3 steuert, kann von der Implementierung to angenommen werden kündigen.157)


157)Dies soll Compiler-Transformationen wie das Entfernen von Leerschleifen auch dann ermöglichen, wenn der Abbruch nicht nachgewiesen werden kann.

Da der steuernde Ausdruck Ihrer Schleife eine Konstante ist, geht der Compiler möglicherweise nicht davon aus, dass die Schleife beendet wird. Dies ist für reaktive Programme gedacht, die wie ein Betriebssystem ewig laufen sollen.

Für die folgende Schleife ist das Verhalten jedoch unklar

a = 1; while(a);

Tatsächlich kann ein Compiler diese Schleife entfernen oder nicht, was zu einem Programm führt, das beendet oder nicht beendet werden kann. Das ist nicht wirklich undefiniert, da es nicht erlaubt ist, Ihre Festplatte zu löschen, aber es ist eine Konstruktion, die Sie vermeiden sollten.

Es gibt jedoch einen weiteren Haken, betrachten Sie den folgenden Code:

a = 1; while(a) while(1);

Da der Compiler nun davon ausgehen kann, dass die äußere Schleife beendet wird, sollte auch die innere Schleife beendet werden, wie sonst könnte die äußere Schleife beendet werden. Wenn Sie also einen wirklich intelligenten Compiler haben, dann a while(1); Schleife, die nicht terminieren soll, muss solche nicht terminierenden Schleifen bis ganz nach oben haben main. Wenn Sie wirklich die Endlosschleife wollen, lesen oder schreiben Sie besser welche volatile variabel darin.

Warum diese Klausel nicht praktikabel ist

Es ist sehr unwahrscheinlich, dass unsere Compiler-Firma jemals von dieser Klausel Gebrauch machen wird, hauptsächlich weil es sich um eine sehr syntaktische Eigenschaft handelt. In der Zwischendarstellung (IR) geht die Differenz zwischen der Konstanten und der Variablen in den obigen Beispielen leicht durch konstante Fortpflanzung verloren.

Die Absicht der Klausel ist es, Compiler-Autoren zu erlauben, wünschenswerte Transformationen wie die folgenden anzuwenden. Betrachten Sie eine nicht so ungewöhnliche Schleife:

int f(unsigned int n, int *a)
{       unsigned int i;
        int s;
        
        s = 0;
        for (i = 10U; i <= n; i++)
        {
                s += a[i];
        }
        return s;
}

Aus architektonischen Gründen (z. B. Hardwareschleifen) möchten wir diesen Code umwandeln in:

int f(unsigned int n, int *a)
{       unsigned int i;
        int s;
        
        s = 0;
        for (i = 0; i < n-9; i++)
        {
                s += a[i+10];
        }
        return s;
}

Ohne Ziffer 6.8.5 ad 6 ist dies nicht möglich, denn wenn n gleich UINT_MAX, die Schleife wird möglicherweise nicht beendet. Trotzdem ist es für einen Menschen ziemlich klar, dass dies nicht die Absicht des Autors dieses Codes ist. Abschnitt 6.8.5 ad 6 erlaubt nun diese Transformation. Die Art und Weise, wie dies erreicht wird, ist jedoch für einen Compiler-Schreiber nicht sehr praktisch, da die syntaktische Anforderung einer Endlosschleife auf dem IR schwer aufrechtzuerhalten ist.

Beachten Sie, dass dies unbedingt erforderlich ist n und i sind unsigned als Überlauf an signed int gibt undefiniertes Verhalten und somit kann die Transformation aus diesem Grund gerechtfertigt werden. Effizienter Code profitiert jedoch von der Verwendung unsignedabgesehen vom größeren positiven Bereich.

Ein alternativer Ansatz

Unser Ansatz wäre, dass der Codeschreiber seine Absicht zum Ausdruck bringen muss, indem er zum Beispiel ein einfügt assert(n < UINT_MAX) vor der Schleife oder einer Frama-C-ähnlichen Garantie. Auf diese Weise kann der Compiler die Terminierung „beweisen“ und muss sich nicht auf Klausel 6.8.5 ad 6 verlassen.

PS: Ich schaue mir einen Entwurf vom 12. April 2011 an, da Paxdiablo eindeutig eine andere Version betrachtet, vielleicht ist seine Version neuer. In seinem Zitat wird das Element des konstanten Ausdrucks nicht erwähnt.

  • Ich schaue mir auch n1570 an, und ich versichere Ihnen, dass das Zitat von Paxdiablo dort am Ende der Seite mit der Nummer 150 (168 in Adobe Reader-Seitenzahlen) ist …

    – autistisch

    8. Mai 2013 um 11:00 Uhr


  • @undefinedbehaviour Ich habe gerade n1570 heruntergeladen und es enthält immer noch die Version in meinem Zitat der Klausel, in der eine Ausnahme für “dessen steuernder Ausdruck kein konstanter Ausdruck ist” gemacht wird. Aber wie ich oben argumentiert habe, hilft es nicht wirklich.

    – Bryan Olivier

    8. Mai 2013 um 11:35 Uhr

  • Ah. Der Zusatz war mir nicht aufgefallen. Sehr gut. Das, was Sie gerade sehen, ist der aktuellste Entwurf des C11-Standards.

    – autistisch

    8. Mai 2013 um 11:42 Uhr

  • Der Compiler ist bereits aus anderen Gründen gezwungen, nachzuverfolgen, ob eine weitergegebene Konstante ein konstanter Ausdruck ist. Zum Beispiel, sizeof(*(char (*)[1])a++) erhöht sich nicht aaber sizeof(*(char (*)[non_constexpr_1])a++) tut.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    8. Mai 2013 um 14:12 Uhr

  • @R .. Das ist ein obskurer Code, ich muss mich damit befassen. Ich bin mir aber ziemlich sicher, dass sich das im Frontend lösen lässt und der Unterschied nicht ins IR wandert.

    – Bryan Olivier

    8. Mai 2013 um 14:23 Uhr

Benutzer-Avatar
entspannen

Nach dem Einchecken den Entwurf des C99-Standards, würde ich “nein” sagen, es ist nicht undefiniert. Ich kann im Entwurf keine Sprache finden, die eine Anforderung erwähnt, dass Iterationen enden.

Der vollständige Text des Absatzes, der die Semantik der Iterationsanweisungen beschreibt, lautet:

Eine Iterationsanweisung bewirkt, dass eine Anweisung namens Schleifenrumpf wiederholt ausgeführt wird, bis der steuernde Ausdruck gleich 0 ist.

Ich würde erwarten, dass dort gegebenenfalls eine Einschränkung wie die für C++ 11 angegebene angezeigt wird. Es gibt auch einen Abschnitt mit dem Namen “Einschränkungen”, der ebenfalls keine solche Einschränkung erwähnt.

Natürlich könnte der tatsächliche Standard etwas anderes sagen, obwohl ich das bezweifle.

  • Die Fortschrittsgarantie wurde in C11 (N1570) hinzugefügt.

    – MM

    28. November 2019 um 1:03 Uhr

Benutzer-Avatar
autistisch

Die einfachste Antwort ist ein Zitat aus §5.1.2.3p6, das die Mindestanforderungen an eine konforme Implementierung angibt:

Die Mindestanforderungen an eine konforme Implementierung sind:

— Zugriffe auf flüchtige Objekte werden streng nach den Regeln der abstrakten Maschine ausgewertet.

— Bei Programmende müssen alle in Dateien geschriebenen Daten mit dem Ergebnis identisch sein, das die Ausführung des Programms gemäß der abstrakten Semantik hervorgebracht hätte.

— Die Eingangs- und Ausgangsdynamik von interaktiven Geräten muss wie in 7.21.3 spezifiziert erfolgen. Die Absicht dieser Anforderungen besteht darin, dass eine ungepufferte oder zeilengepufferte Ausgabe so schnell wie möglich erscheint, um sicherzustellen, dass Aufforderungsmeldungen tatsächlich erscheinen, bevor ein Programm auf eine Eingabe wartet.

Dies ist das beobachtbare Verhalten des Programms.

Wenn der Maschinencode aufgrund von durchgeführten Optimierungen nicht das beobachtbare Verhalten erzeugt, dann ist der Compiler kein C-Compiler. Was ist das beobachtbare Verhalten eines Programms, das nur eine solche Endlosschleife enthält, am Punkt der Beendigung? Eine solche Schleife könnte nur durch ein Signal enden, das sie vorzeitig beendet. Im Falle des SIGTERM, wird das Programm beendet. Dies würde kein beobachtbares Verhalten hervorrufen. Daher besteht die einzig gültige Optimierung dieses Programms darin, dass der Compiler dem System zuvorkommt, das Programm zu schließen, und ein Programm generiert, das sofort endet.

/* unoptimised version */
int main() {
    for (;;);
    puts("The loop has ended");
}

/* optimised version */
int main() { }

Eine Möglichkeit besteht darin, dass ein Signal ausgelöst und longjmp aufgerufen wird, damit die Ausführung an eine andere Stelle springt. Es scheint, als ob der einzige Ort, an den gesprungen werden könnte, irgendwo während der Ausführung vor der Schleife erreicht wird. Vorausgesetzt, der Compiler ist intelligent genug, um zu bemerken, dass ein Signal ausgelöst wird, das dazu führt, dass die Ausführung an einen anderen Ort springt, könnte er möglicherweise die Schleife optimieren (und das Signal steigt) weg, um sofort zu springen.

Wenn mehrere Threads in die Gleichung eingehen, kann eine gültige Implementierung möglicherweise den Besitz des Programms vom Hauptthread auf einen anderen Thread übertragen und den Hauptthread beenden. Das beobachtbare Verhalten des Programms muss unabhängig von Optimierungen weiterhin beobachtbar sein.

  • Ihr Name ist fast wie ein Neuheitskonto für diese Frage.

    – Toni der Löwe

    8. Mai 2013 um 10:41 Uhr

Benutzer-Avatar
paxdiablo

Die folgende Erklärung erscheint in C11 6.8.5 Iteration statements /6:

Eine Iterationsanweisung, deren steuernder Ausdruck kein konstanter Ausdruck ist, die keine Ein-/Ausgabeoperationen ausführt, nicht auf flüchtige Objekte zugreift und keine Synchronisierung oder atomaren Operationen in ihrem Hauptteil, dem steuernden Ausdruck oder (im Fall einer for-Anweisung) durchführt. sein Ausdruck-3, kann von der Implementierung als beendet angenommen werden.

Seit while(1); Verwendet ein konstanter Ausdruck ist, darf die Implementierung nicht davon ausgehen, dass sie beendet wird.

Ein Compiler ist frei, eine solche Schleife vollständig zu entfernen, wenn der Ausdruck nicht konstant ist und alle anderen Bedingungen ebenfalls erfüllt sind, selbst wenn nicht schlüssig bewiesen werden kann, dass die Schleife terminieren würde.

1376280cookie-checkIst während (1); undefiniertes Verhalten in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy