Reihenfolge der Auswertung der Array-Indizes (gegenüber dem Ausdruck) in C.

47

Betrachten Sie diesen Code:

static int global_var = 0;

int update_three(int val)
{
    global_var = val;
    return 3;
}

int main()
{
    int arr[5];
    arr[global_var] = update_three(2);
}

Welcher Array-Eintrag wird aktualisiert? 0 oder 2?

Gibt es einen Teil in der Spezifikation von C, der den Vorrang des Betriebs in diesem speziellen Fall angibt?

Jiminion
quelle
21
Dies riecht nach undefiniertem Verhalten. Es ist sicherlich etwas, das niemals absichtlich codiert werden sollte.
Fiddling Bits
1
Ich bin damit einverstanden, dass dies ein Beispiel für eine schlechte Codierung ist.
Jiminion
4
Einige anekdotische Ergebnisse: godbolt.org/z/hM2Jo2
Bob__
15
Dies hat nichts mit Array-Indizes oder der Reihenfolge der Operationen zu tun. Dies hat mit dem zu tun, was die C-Spezifikation "Sequenzpunkte" nennt, und insbesondere mit der Tatsache, dass Zuweisungsausdrücke KEINEN Sequenzpunkt zwischen dem linken und dem rechten Ausdruck erzeugen, sodass der Compiler frei tun kann, dies zu tun wählt.
Lee Daniel Crocker
4
Sie sollten eine Funktionsanforderung an melden, clangdamit dieser Code IMHO eine Warnung auslöst.
Malat

Antworten:

51

Reihenfolge der linken und rechten Operanden

Um die Zuweisung in arr[global_var] = update_three(2)durchzuführen, muss die C-Implementierung die Operanden auswerten und als Nebeneffekt den gespeicherten Wert des linken Operanden aktualisieren. C 2018 6.5.16 (in dem es um Zuweisungen geht) In Absatz 3 heißt es, dass der linke und der rechte Operand keine Sequenzierung aufweisen:

Die Auswertungen der Operanden sind nicht sequenziert.

Dies bedeutet, dass die C-Implementierung frei ist, zuerst den l-Wert zu berechnen arr[global_var](indem wir den l-Wert berechnen, meinen wir herauszufinden, worauf sich dieser Ausdruck bezieht), dann zu bewerten update_three(2)und schließlich den Wert des letzteren dem ersteren zuzuweisen; oder zuerst zu bewerten update_three(2), dann den l-Wert zu berechnen und dann den ersteren dem letzteren zuzuweisen; oder um den l- update_three(2)Wert in einer vermischten Weise zu bewerten und dann dem linken l-Wert den richtigen Wert zuzuweisen.

In allen Fällen muss die Zuordnung des Wertes zum Wert zuletzt erfolgen, da 6.5.16 3 auch sagt:

… Der Nebeneffekt der Aktualisierung des gespeicherten Werts des linken Operanden wird nach den Wertberechnungen des linken und rechten Operanden sequenziert…

Sequenzierungsverletzung

Einige denken möglicherweise über undefiniertes Verhalten nach, da global_vares unter Verstoß gegen 6.5 2 sowohl verwendet als auch separat aktualisiert wird.

Wenn eine Nebenwirkung auf ein Skalarobjekt in Bezug auf eine andere Nebenwirkung auf dasselbe Skalarobjekt oder eine Wertberechnung unter Verwendung des Werts desselben Skalarobjekts nicht sequenziert wird, ist das Verhalten undefiniert.

Vielen C-Praktikern ist bekannt, dass das Verhalten von Ausdrücken, wie x + x++es nicht durch den C-Standard definiert ist, da sie den Wert von verwenden xund ihn im selben Ausdruck ohne Sequenzierung separat modifizieren. In diesem Fall haben wir jedoch einen Funktionsaufruf, der eine gewisse Sequenzierung bietet. global_varwird in verwendet arr[global_var]und im Funktionsaufruf aktualisiert update_three(2).

6.5.2.2 10 sagt uns, dass es einen Sequenzpunkt gibt, bevor die Funktion aufgerufen wird:

Es gibt einen Sequenzpunkt nach den Auswertungen des Funktionsbezeichners und den tatsächlichen Argumenten, aber vor dem eigentlichen Aufruf…

Innerhalb der Funktion global_var = val;befindet sich ein vollständiger Ausdruck , ebenso wie das 3In return 3;gemäß 6.8 4:

Ein vollständiger Ausdruck ist ein Ausdruck, der weder Teil eines anderen Ausdrucks noch Teil eines Deklarators oder abstrakten Deklarators ist.

Dann gibt es einen Sequenzpunkt zwischen diesen beiden Ausdrücken, wiederum gemäß 6.8 4:

… Es gibt einen Sequenzpunkt zwischen der Bewertung eines vollständigen Ausdrucks und der Bewertung des nächsten zu bewertenden vollständigen Ausdrucks.

Somit kann die C-Implementierung arr[global_var]zuerst auswerten und dann den Funktionsaufruf ausführen. In diesem Fall befindet sich ein Sequenzpunkt zwischen ihnen, da sich vor dem Funktionsaufruf einer befindet, oder sie kann global_var = val;im Funktionsaufruf auswerten und dann arr[global_var], in welchem ​​Fall ein Sequenzpunkt zwischen ihnen, weil es einen nach dem vollständigen Ausdruck gibt. Das Verhalten ist also nicht spezifiziert - eines dieser beiden Dinge kann zuerst bewertet werden -, aber es ist nicht undefiniert.

Eric Postpischil
quelle
24

Das Ergebnis ist hier nicht spezifiziert .

Während die Reihenfolge der Operationen in einem Ausdruck, die bestimmen, wie Unterausdrücke gruppiert werden, genau definiert ist, ist die Reihenfolge der Auswertung nicht angegeben. In diesem Fall bedeutet dies, dass entweder global_varzuerst gelesen werden kann oder der Aufruf von update_threezuerst erfolgen kann, aber es gibt keine Möglichkeit zu wissen, welche.

Hier gibt es kein undefiniertes Verhalten, da ein Funktionsaufruf einen Sequenzpunkt einführt, ebenso wie jede Anweisung in der Funktion, einschließlich derjenigen, die geändert wird global_var.

Zur Verdeutlichung definiert der C-Standard undefiniertes Verhalten in Abschnitt 3.4.3 als:

undefiniertes Verhalten

Verhalten bei Verwendung eines nicht portierbaren oder fehlerhaften Programmkonstrukts oder von fehlerhaften Daten, für die diese Internationale Norm keine Anforderungen stellt

und definiert nicht spezifiziertes Verhalten in Abschnitt 3.4.4 als:

nicht näher bezeichnetes Verhalten

Verwendung eines nicht spezifizierten Wertes oder eines anderen Verhaltens, bei dem diese Internationale Norm zwei oder mehr Möglichkeiten bietet und keine weiteren Anforderungen stellt, die in irgendeinem Fall gewählt werden

Der Standard besagt, dass die Auswertungsreihenfolge der Funktionsargumente nicht angegeben ist, was in diesem Fall bedeutet, dass entweder arr[0]auf 3 oder arr[2]auf 3 gesetzt wird.

dbush
quelle
"Ein Funktionsaufruf führt einen Sequenzpunkt ein" ist unzureichend. Wenn der linke Operand zuerst ausgewertet wird, reicht dies aus, da dann der Sequenzpunkt den linken Operanden von den Auswertungen in der Funktion trennt. Wenn jedoch der linke Operand nach dem Funktionsaufruf ausgewertet wird, liegt der Sequenzpunkt aufgrund des Aufrufs der Funktion nicht zwischen den Auswertungen in der Funktion und der Auswertung des linken Operanden. Sie benötigen auch den Sequenzpunkt, der die vollständigen Ausdrücke trennt.
Eric Postpischil
2
@EricPostpischil In der Terminologie vor C11 gibt es einen Sequenzpunkt beim Ein- und Ausstieg einer Funktion. In der C11-Terminologie wird der gesamte Funktionskörper in Bezug auf den aufrufenden Kontext unbestimmt sequenziert. Beide spezifizieren dasselbe und verwenden nur unterschiedliche Begriffe
MM
Das ist absolut falsch. Die Reihenfolge der Bewertung der Argumente der Zuordnung ist nicht angegeben. Das Ergebnis dieser speziellen Zuweisung ist die Erstellung eines Arrays mit einem unzuverlässigen Inhalt, der sowohl nicht portierbar als auch an sich falsch ist (inkonsistent mit der Semantik oder einem der beabsichtigten Ergebnisse). Ein perfekter Fall von undefiniertem Verhalten.
Kuroi Neko
1
@kuroineko Nur weil die Ausgabe variieren kann, ist das Verhalten nicht automatisch undefiniert. Der Standard hat unterschiedliche Definitionen für undefiniertes oder nicht spezifiziertes Verhalten, und in dieser Situation ist es das letztere.
dbush
@EricPostpischil Hier haben Sie Sequenzpunkte (aus C11 informativem Anhang C): "Zwischen den Auswertungen des Funktionsbezeichners und den tatsächlichen Argumenten in einem Funktionsaufruf und dem tatsächlichen Aufruf. (6.5.2.2)", "Zwischen der Auswertung eines vollständigen Ausdrucks und der nächste vollständige Ausdruck, der ausgewertet werden soll ... / - / ... der (optionale) Ausdruck in einer return-Anweisung (6.8.6.4) ". Und nun, auch bei jedem Semikolon, da dies ein vollständiger Ausdruck ist.
Lundin
1

Ich habe es versucht und den Eintrag 0 aktualisiert.

Nach dieser Frage wird jedoch die rechte Seite eines Ausdrucks immer zuerst ausgewertet

Die Reihenfolge der Bewertung ist nicht spezifiziert und nicht geordnet. Daher denke ich, dass ein solcher Code vermieden werden sollte.

Mickael B.
quelle
Ich habe das Update auch bei Eintrag 0 erhalten.
Jiminion
1
Das Verhalten ist nicht undefiniert, aber nicht spezifiziert. Natürlich sollte je nach beidem vermieden werden.
Antti Haapala
@AnttiHaapala Ich habe bearbeitet
Mickael B.
1
Hmm ah und es ist nicht nicht sequenziert, sondern unbestimmt sequenziert ... 2 Personen, die zufällig in einer Warteschlange stehen, sind unbestimmt sequenziert. Neo in Agent Smith sind nicht sequenziert und es kommt zu undefiniertem Verhalten.
Antti Haapala
0

Da es wenig sinnvoll ist, Code für eine Zuweisung auszugeben, bevor Sie einen Wert zuweisen müssen, geben die meisten C-Compiler zuerst Code aus, der die Funktion aufruft, und speichern das Ergebnis irgendwo (Register, Stapel usw.), dann geben sie Code aus schreibt diesen Wert an sein endgültiges Ziel und liest daher die globale Variable, nachdem sie geändert wurde. Nennen wir dies die "natürliche Ordnung", die nicht durch irgendeinen Standard, sondern durch reine Logik definiert ist.

Während des Optimierungsprozesses versuchen die Compiler jedoch, den Zwischenschritt des vorübergehenden Speicherns des Werts irgendwo zu eliminieren und das Funktionsergebnis so direkt wie möglich in das endgültige Ziel zu schreiben. In diesem Fall müssen sie häufig zuerst den Index lesen B. in ein Register, um das Funktionsergebnis direkt in das Array verschieben zu können. Dies kann dazu führen, dass die globale Variable gelesen wird, bevor sie geändert wurde.

Dies ist also im Grunde ein undefiniertes Verhalten mit der sehr schlechten Eigenschaft, dass es sehr wahrscheinlich ist, dass das Ergebnis unterschiedlich ist, je nachdem, ob eine Optimierung durchgeführt wird und wie aggressiv diese Optimierung ist. Es ist Ihre Aufgabe als Entwickler, dieses Problem durch eine der folgenden Codierungen zu beheben:

int idx = global_var;
arr[idx] = update_three(2);

oder Kodierung:

int temp = update_three(2);
arr[global_var] = temp;

Als gute Faustregel gilt: Wenn globale Variablen nicht vorhanden sind const(oder nicht, aber Sie wissen, dass kein Code sie jemals als Nebeneffekt ändern wird), sollten Sie sie niemals direkt im Code verwenden, wie in einer Umgebung mit mehreren Threads. Auch dies kann undefiniert sein:

int result = global_var + (2 * global_var);
// Is not guaranteed to be equal to `3 * global_var`!

Da der Compiler es möglicherweise zweimal liest und ein anderer Thread den Wert zwischen den beiden Lesevorgängen ändern kann. Wiederum würde eine Optimierung definitiv dazu führen, dass der Code ihn nur einmal liest, sodass Sie möglicherweise wieder andere Ergebnisse erzielen, die jetzt auch vom Timing eines anderen Threads abhängen. Somit haben Sie viel weniger Kopfschmerzen, wenn Sie globale Variablen vor der Verwendung in einer temporären Stapelvariablen speichern. Denken Sie daran, wenn der Compiler dies für sicher hält, wird er höchstwahrscheinlich auch das optimieren und stattdessen die globale Variable direkt verwenden, sodass es letztendlich keinen Unterschied in der Leistung oder der Speichernutzung machen kann.

(Nur für den Fall, dass jemand fragt, warum jemand dies tun x + 2 * xsollte 3 * x- bei einigen CPUs ist die Addition ultraschnell, ebenso wie die Multiplikation mit einer Potenz zwei, da der Compiler diese in Bitverschiebungen umwandelt ( 2 * x == x << 1). Die Multiplikation mit beliebigen Zahlen kann jedoch sehr langsam sein Anstatt mit 3 zu multiplizieren, erhalten Sie viel schnelleren Code, indem Sie x um 1 bitverschieben und x zum Ergebnis hinzufügen - und selbst dieser Trick wird von modernen Compilern ausgeführt, wenn Sie mit 3 multiplizieren und die aggressive Optimierung aktivieren, es sei denn, es handelt sich um ein modernes Ziel CPU, bei der die Multiplikation genauso schnell ist wie die Addition, da der Trick die Berechnung verlangsamen würde.)

Mecki
quelle
2
Es ist kein undefiniertes Verhalten - der Standard listet Möglichkeiten auf und eine davon wird auf jeden Fall ausgewählt
Antti Haapala
Der Compiler wird nicht 3 * xin zwei Lesevorgänge von x umgewandelt. Es könnte x einmal lesen und dann die x + 2 * x-Methode in dem Register
MM
6
@Mecki "Wenn Sie nicht sagen können, was das Ergebnis ist, indem Sie nur den Code betrachten, ist das Ergebnis undefiniert" - undefiniertes Verhalten hat in C / C ++ eine sehr spezifische Bedeutung, und das ist es nicht. Andere Antwortende haben erklärt, warum diese bestimmte Instanz nicht spezifiziert , aber nicht undefiniert ist .
Marcelm
3
Ich schätze die Absicht, etwas Licht in die Interna eines Computers zu bringen, auch wenn dies über den Rahmen der ursprünglichen Frage hinausgeht. UB ist jedoch eine sehr präzise C / C ++ - Fachsprache und sollte sorgfältig verwendet werden, insbesondere wenn es um sprachtechnische Fragen geht. Sie könnten stattdessen den richtigen Begriff "nicht spezifiziertes Verhalten" verwenden, um die Antwort erheblich zu verbessern.
Kuroi Neko
2
@Mecki "Undefiniert hat eine ganz besondere Bedeutung in der englischen Sprache " ... aber in einer Frage mit der Bezeichnung language-lawyer, in der die betreffende Sprache eine eigene "ganz besondere Bedeutung" für undefiniert hat , werden Sie nur dann Verwirrung stiften , wenn Sie sie nicht verwenden die Definition der Sprache.
TripeHound
-1

Globale Bearbeitung: Tut mir leid, Leute, ich war total begeistert und habe viel Unsinn geschrieben. Nur ein alter Knacker, der schimpft.

Ich wollte glauben, dass C verschont wurde, aber leider wurde es seit C11 mit C ++ gleichgesetzt. Um zu wissen, was der Compiler mit Nebenwirkungen in Ausdrücken tun wird, muss offenbar ein kleines mathematisches Rätsel gelöst werden, das eine teilweise Anordnung von Codesequenzen basierend auf einem "befindet sich vor dem Synchronisationspunkt von" beinhaltet.

Ich habe in den Tagen von K & R zufällig einige wichtige eingebettete Echtzeitsysteme entworfen und implementiert (einschließlich der Steuerung eines Elektroautos, das Menschen gegen die nächste Wand krachen lassen könnte, wenn der Motor nicht in Schach gehalten würde, ein 10-Tonnen-Industriemotor Roboter, der Menschen zu Brei zerquetschen könnte, wenn er nicht richtig befohlen wird, und eine Systemschicht, die, obwohl harmlos, ein paar Dutzend Prozessoren dazu bringt, ihren Datenbus mit weniger als 1% Systemaufwand trocken zu saugen).

Ich bin vielleicht zu senil oder dumm, um den Unterschied zwischen undefiniert und nicht spezifiziert zu erkennen, aber ich denke, ich habe immer noch eine ziemlich gute Vorstellung davon, was gleichzeitige Ausführung und Datenzugriff bedeuten. Meiner wohl informierten Meinung nach ist diese Besessenheit der C ++ - und jetzt C-Leute mit ihren Lieblingssprachen, die Synchronisationsprobleme übernehmen, ein kostspieliger Wunschtraum. Entweder wissen Sie, was gleichzeitige Ausführung ist, und Sie brauchen keines dieser Dinge, oder Sie tun es nicht, und Sie würden der ganzen Welt einen Gefallen tun, ohne zu versuchen, sich damit anzulegen.

All diese vielen atemberaubenden Abstraktionen von Speicherbarrieren sind einfach auf eine vorübergehende Reihe von Einschränkungen der Multi-CPU-Cache-Systeme zurückzuführen, die alle sicher in gängigen Betriebssystemsynchronisationsobjekten wie beispielsweise den Mutexen und Bedingungsvariablen C ++ eingekapselt werden können bietet an.
Die Kosten für diese Kapselung sind nur ein winziger Leistungsabfall im Vergleich zu einigen Fällen, die durch die Verwendung feinkörniger spezifischer CPU-Anweisungen erzielt werden könnten.
Das volatileSchlüsselwort (oder a#pragma dont-mess-with-that-variableNach allem, was ich als Systemprogrammierer getan habe, wäre es völlig ausreichend gewesen, dem Compiler zu sagen, er solle aufhören, Speicherzugriffe neu zu ordnen. Mit direkten asm-Anweisungen kann problemlos optimaler Code erstellt werden, um Treiber- und Betriebssystemcode auf niedriger Ebene mit Ad-hoc-CPU-spezifischen Anweisungen zu versehen. Ohne genaue Kenntnis der Funktionsweise der zugrunde liegenden Hardware (Cache-System oder Busschnittstelle) müssen Sie ohnehin nutzlosen, ineffizienten oder fehlerhaften Code schreiben.

Eine winzige Anpassung des volatileSchlüsselworts und von Bob wäre jeder gewesen, außer dem Onkel des hartgesottensten Low-Level-Programmierers. Stattdessen hatte die übliche Gruppe von C ++ - Mathematikfreaks einen großen Tag damit verbracht, eine weitere unverständliche Abstraktion zu entwerfen, die ihrer typischen Tendenz entsprach, Lösungen zu entwerfen, die nach nicht existierenden Problemen suchen und die Definition einer Programmiersprache mit den Spezifikationen eines Compilers verwechseln.

Nur dieses Mal war die Änderung erforderlich, um auch einen grundlegenden Aspekt von C zu entstellen, da diese "Barrieren" selbst in C-Code auf niedriger Ebene generiert werden mussten, um ordnungsgemäß zu funktionieren. Dies hat unter anderem die Definition von Ausdrücken ohne jegliche Erklärung oder Rechtfertigung verwüstet.

Zusammenfassend ist die Tatsache, dass ein Compiler aus diesem absurden Teil von C einen konsistenten Maschinencode erzeugen könnte, nur eine entfernte Folge der Art und Weise, wie C ++ - Leute mit möglichen Inkonsistenzen der Cache-Systeme der späten 2000er Jahre fertig wurden.
Ein grundlegender Aspekt von C (Ausdrucksdefinition) wurde schrecklich durcheinander gebracht, so dass die überwiegende Mehrheit der C-Programmierer - die sich zu Recht nicht um Cache-Systeme kümmern - nun gezwungen ist, sich auf Gurus zu verlassen, um das zu erklären Unterschied zwischen a = b() + c()und a = b + c.

Der Versuch zu erraten, was aus diesem unglücklichen Array werden wird, ist ohnehin ein Nettoverlust an Zeit und Mühe. Unabhängig davon, was der Compiler daraus machen wird, ist dieser Code pathologisch falsch. Die einzige verantwortliche Sache, die damit zu tun hat, ist, es in den Papierkorb zu schicken.
Konzeptionell können Nebenwirkungen immer aus Ausdrücken entfernt werden, mit dem trivialen Aufwand, die Änderung explizit vor oder nach der Bewertung in einer separaten Anweisung erfolgen zu lassen.
Diese Art von beschissenem Code könnte in den 80er Jahren gerechtfertigt gewesen sein, als man nicht erwarten konnte, dass ein Compiler irgendetwas optimiert. Aber jetzt, da Compiler längst schlauer geworden sind als die meisten Programmierer, bleibt nur noch ein Stück beschissener Code übrig.

Ich verstehe auch nicht, wie wichtig diese undefinierte / nicht näher bezeichnete Debatte ist. Entweder können Sie sich darauf verlassen, dass der Compiler Code mit einem konsistenten Verhalten generiert, oder Sie können nicht. Ob Sie das als undefiniert oder nicht spezifiziert bezeichnen, scheint ein strittiger Punkt zu sein.

Meiner wohl informierten Meinung nach ist C in seinem K & R-Zustand bereits gefährlich genug. Eine nützliche Entwicklung wäre das Hinzufügen von Sicherheitsmaßnahmen mit gesundem Menschenverstand. Wenn Sie beispielsweise dieses erweiterte Code-Analyse-Tool verwenden, zwingen die Spezifikationen den Compiler zur Implementierung, um zumindest Warnungen über Bonkers-Code zu generieren, anstatt stillschweigend einen Code zu generieren, der möglicherweise extrem unzuverlässig ist.
Stattdessen beschlossen die Jungs zum Beispiel, eine feste Auswertungsreihenfolge in C ++ 17 zu definieren. Jetzt wird jeder Software-Idiot aktiv dazu angeregt, absichtlich Nebenwirkungen in seinen Code zu setzen, und sich darauf verlassen, dass die neuen Compiler die Verschleierung auf deterministische Weise eifrig handhaben werden.

K & R war eines der wahren Wunder der Computerwelt. Für zwanzig Dollar haben Sie eine umfassende Spezifikation der Sprache (ich habe gesehen, wie einzelne Personen komplette Compiler geschrieben haben, nur mit diesem Buch), ein ausgezeichnetes Nachschlagewerk (das Inhaltsverzeichnis würde Sie normalerweise auf ein paar Seiten der Antwort auf Ihre Frage verweisen Frage) und ein Lehrbuch, in dem Sie lernen, die Sprache auf vernünftige Weise zu verwenden. Vervollständigen Sie mit Begründungen, Beispielen und weisen Worten der Warnung vor den zahlreichen Möglichkeiten, wie Sie die Sprache missbrauchen können, um sehr, sehr dumme Dinge zu tun.

Dieses Erbe für so wenig Gewinn zu zerstören, scheint mir eine grausame Verschwendung zu sein. Aber auch hier könnte ich den Punkt sehr wohl nicht vollständig verstehen. Vielleicht könnte mich eine freundliche Seele in die Richtung eines Beispiels für neuen C-Code weisen, der diese Nebenwirkungen erheblich ausnutzt?

kuroi neko
quelle
Es ist ein undefiniertes Verhalten, wenn im selben Ausdruck, C17 6.5 / 2, Nebenwirkungen auf dasselbe Objekt auftreten. Diese sind gemäß C17 6.5.18 / 3 nicht sequenziert. Aber der Text aus 6.5 / 2 "Wenn eine Nebenwirkung auf ein Skalarobjekt in Bezug auf eine andere Nebenwirkung auf dasselbe Skalarobjekt oder eine Wertberechnung unter Verwendung des Werts desselben Skalarobjekts nicht sequenziert wird, ist das Verhalten undefiniert." gilt nicht, da die Wertberechnung innerhalb der Funktion entweder vor oder nach dem Array-Indexzugriff sequenziert wird, unabhängig davon, ob der Zuweisungsoperator nicht sequenzierte Operanden an sich hat.
Lundin
Der Funktionsaufruf verhält sich, wenn Sie so wollen, wie "ein Mutex gegen nicht sequenzierten Zugriff". Ähnlich wie obskure Komma Operator Mist wie 0,expr,0.
Lundin
Ich denke, Sie haben den Autoren des Standards geglaubt, als sie sagten: "Undefiniertes Verhalten gibt dem Implementierer die Lizenz, bestimmte Programmfehler, die schwer zu diagnostizieren sind, nicht abzufangen. Außerdem werden Bereiche mit möglichen konformen Spracherweiterungen identifiziert: Der Implementierer kann die Sprache durch die Bereitstellung von a erweitern Definition des offiziell undefinierten Verhaltens . " und sagte, der Standard sollte keine nützlichen Programme herabsetzen, die nicht streng konform waren. Ich denke, die meisten Autoren des Standards hätten es für offensichtlich gehalten, dass Leute, die hochwertige Compiler schreiben wollen ...
supercat
... sollten versuchen, UB als Gelegenheit zu nutzen, um ihre Compiler für ihre Kunden so nützlich wie möglich zu machen. Ich bezweifle, dass sich irgendjemand hätte vorstellen können, dass Compiler-Autoren es als Ausrede verwenden würden, um auf Beschwerden zu antworten, dass "Ihr Compiler diesen Code weniger nützlich verarbeitet als alle anderen" mit "Das liegt daran, dass der Standard nicht verlangt, dass wir ihn nützlich verarbeiten, und Implementierungen dass Programme, deren Verhalten nicht durch den Standard vorgeschrieben ist, sinnvoll verarbeitet werden, fördern lediglich das Schreiben fehlerhafter Programme. "
Supercat
Ich verstehe den Punkt in Ihrer Bemerkung nicht. Das Verlassen auf compilerspezifisches Verhalten ist eine Garantie für die Nichtportabilität. Es erfordert auch großes Vertrauen in den Compilerhersteller, der jede dieser "zusätzlichen Definitionen" jederzeit einstellen kann. Das einzige, was ein Compiler tun kann, ist, Warnungen zu generieren, die ein weiser und sachkundiger Programmierer möglicherweise als Fehler behandelt. Das Problem, das ich bei diesem ISO-Monster sehe, ist, dass es solch grausamen Code wie das Beispiel des OP legitim macht (aus äußerst unklaren Gründen im Vergleich zur K & R-Definition eines Ausdrucks).
kuroi neko