Ist es sicher, Inkrement-/Dekrementoperatoren in ternäre/bedingte Operatoren einzufügen?

Lesezeit: 5 Minuten

Hier ist ein Beispiel

#include <iostream>
using namespace std;
int main()
{   
    int x = 0;
    cout << (x == 0 ? x++ : x) << endl; //operator in branch
    cout << "x=" << x << endl;
    cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
    cout << "x=" << x << endl;
    return 0;
}

Ausgang:

0
x=1
1
x=1

Ich verstehe die Ausgabe, aber Ist das undefiniertes Verhalten oder nicht? Ist die Reihenfolge der Auswertung in jedem Fall gewährleistet?

Selbst wenn dies garantiert ist, bin ich mir ziemlich bewusst, dass die Verwendung von Inkrement/Dekrement schnell zu einem Problem für die Lesbarkeit werden kann. Ich frage nur, da ich ähnlichen Code gesehen habe und sofort unsicher war, da es viele Beispiele für die mehrdeutige/undefinierte Verwendung von Inkrement-/Dekrementoperatoren gibt, wie z.

  • C++ definiert nicht die Reihenfolge, in der Funktionsparameter ausgewertet werden.

    int nValue = Add(x, ++x);
    
  • Die Sprache C++ sagt, dass Sie eine Variable nicht mehr als einmal zwischen Sequenzpunkten ändern können.

     x = ++y + y++
    
  • Da Inkrement- und Dekrementoperatoren Nebenwirkungen haben, kann die Verwendung von Ausdrücken mit Inkrement- oder Dekrementoperatoren in einem Präprozessormakro zu unerwünschten Ergebnissen führen.

     #define max(a,b) ((a)<(b))?(b):(a)
     k = max( ++i, j );
    

  • Verwandte (kein Duplikat): stackoverflow.com/questions/10995445/… – Beschreibt einen Sonderfall der Rückzuweisung in die inkrementierte Variable.

    – Suma

    8. September 2014 um 13:59 Uhr

  • Hinweis: Gut definiert ist nur eine Frage. Wartbar ist eine andere. Wenn Sie uns fragen müssen, wie wird die nächste Person, die diesen Code liest, sicher sein, dass er sicher ist? “Echte Autoren schreiben um, um das Problem zu vermeiden.”

    – Keschlam

    9. September 2014 um 3:12 Uhr

  • Der Dekrementoperator in Zeile 4 von main() ist in diesem Beispiel unerheblich, weil das Kurzschlussverhalten von || wird die verursachen --x ganz zu überspringen.

    – JLRishe

    9. September 2014 um 7:43 Uhr

  • @JLRishe eigentlich dreht sich die Frage darum: Ist der Kurzschluss so garantiert --x wird nie ausgewertet? (unten beantwortet)

    – jozxyqk

    9. September 2014 um 7:53 Uhr

  • @jozxyqk Ok, aber in diesem Fall hat es nichts mit ternären/bedingten Operatoren und alles mit dem zu tun || Operator. x == 1 || --x == 0 vollständig ausgewertet wird, bevor der Bedingungsoperator beteiligt ist, und zu diesem Zeitpunkt ist die --x wird bereits übersprungen. Mit anderen Worten, Zeile 4 sagt uns nichts Nichttriviales über Bedingungsoperatoren.

    – JLRishe

    9. September 2014 um 7:55 Uhr


Benutzer-Avatar
TC

Für den Bedingungsoperator (§5.16 [expr.cond]/p1):

Jede Wertberechnung und Nebenwirkung, die dem ersten Ausdruck zugeordnet ist, wird vor jeder Wertberechnung und Nebenwirkung, die dem zweiten oder dritten Ausdruck zugeordnet ist, sequenziert.

Für den logischen ODER-Operator (§5.15 [expr.log.or]/p1-2):

der zweite Operand wird nicht ausgewertet, wenn der erste Operand zu ausgewertet wird true.
[…] Wenn der zweite Ausdruck ausgewertet wird, wird jede Wertberechnung und Nebenwirkung, die dem ersten Ausdruck zugeordnet ist, vor jeder Wertberechnung und Nebenwirkung, die dem zweiten Ausdruck zugeordnet ist, sequenziert.

Das Verhalten Ihres Codes ist wohldefiniert.

Benutzer-Avatar
Goldesel

Es gibt eine garantierte Ausführungsreihenfolge in ternären Operatoren und booleschen Operatoren && und || Operationen, sodass es keinen Konflikt bei den Bewertungssequenzpunkten gibt.

Eins nach dem anderen

 cout << (x == 0 ? x++ : x) << endl; //operator in branch

Wird immer ausgegeben x aber wird es nur erhöhen, wenn es 0 war.

 cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

Dies ist auch wohldefiniert, wenn x 1 war, wird RHS nicht ausgewertet, wenn nicht, wird es dekrementiert, aber --x wird niemals 0 sein, also wird es wahr sein, wenn x==1, in diesem Fall x wird jetzt auch 0 sein.

Im letzteren Fall, wenn x ist INT_MIN Es ist kein wohldefiniertes Verhalten, es zu dekrementieren (und es würde ausgeführt werden).

Das kann im ersten Fall nicht passieren, wo x nicht 0 sein wird, wenn es so ist INT_MAX Sie sind also sicher.

  • Randfall, aber natürlich hätte das zweite Code-Snippet UB if x ist INT_MIN wenn es ausgeführt wird. Das äquivalente Problem mit INT_MAX kann das erste Snippet nicht beeinflussen.

    – Steve Jessop

    8. September 2014 um 14:06 Uhr


Benutzer-Avatar
hackt

Ich verstehe die Ausgabe, aber ist dieses Verhalten undefiniert oder nicht?

Code ist perfekt definiert. C11-Standard sagt:

6.5.15 Bedingter Operator

Der erste Operand wird ausgewertet; es gibt einen Sequenzpunkt zwischen seiner Auswertung und der Auswertung des zweiten oder dritten Operanden (je nachdem, was ausgewertet wird). Der zweite Operand wird nur ausgewertet, wenn der erste ungleich ist 0; der dritte Operand wird nur ausgewertet, wenn der erste gleich ist 0; das Ergebnis ist der Wert des zweiten oder dritten Operanden (je nachdem, welcher ausgewertet wird), umgewandelt in den unten beschriebenen Typ.110)

6.5.14 Logischer OR-Operator

Im Gegensatz zum bitweisen | Operator, das || Operator garantiert eine Links-nach-Rechts-Auswertung; wenn der zweite Operand ausgewertet wird, zwischen den Auswertungen des ersten und zweiten Operanden gibt es einen Sequenzpunkt. Wenn der erste Operand ungleich ist 0der zweite Operand wird nicht ausgewertet.

Des Weiteren wiki erklärt es am Beispiel:

  • Zwischen Auswertung des linken und rechten Operanden der && (logisches UND), || (logisches ODER) (im Rahmen der Kurzschlussauswertung) und comma operators. Zum Beispiel im Ausdruck *p++ != 0 && *q++ != 0alle Nebenwirkungen des Unterausdrucks *p++ != 0 abgeschlossen sind, bevor versucht wird, darauf zuzugreifen q.

  • Zwischen der Auswertung des ersten Operanden des ternären “Fragezeichen”-Operators und des zweiten oder dritten Operanden. Zum Beispiel im Ausdruck a = (*p++) ? (*p++) : 0 Es gibt einen Sequenzpunkt nach dem ersten *p++was bedeutet, dass es zum Zeitpunkt der Ausführung der zweiten Instanz bereits inkrementiert wurde.

Die Regel für || und ?: ist für C++ (Abschnitte 5.15 und 5.16) dasselbe wie in C.


Ist die Reihenfolge der Auswertung in jedem Fall gewährleistet?

Ja. Die Reihenfolge der Auswertung der Operanden von Operatoren ||, &&, , und ?: ist garantiert von links nach rechts.

  • @Slava; Oh! Wie ist es irreführend? Könntest du erklären ?

    – Hacken

    8. September 2014 um 14:00 Uhr


  • Es gibt 2 Beispiele im Code, das zweite hat UB imo. Ich könnte mich irren, aber die Antwort beantwortet es sowieso nicht, da es darauf basiert, ob der boolesche Operator einen Sequenzpunkt hat.

    – Slava

    8. September 2014 um 14:00 Uhr


  • @Slava; Welche hat UB?

    – Hacken

    8. September 2014 um 14:04 Uhr

  • @hackks es gibt kein UB ich habe mich geirrt. Ihre Antwort erwähnt jedoch nicht, warum das zweite Beispiel kein UB hat, dh es gibt einen Sequenzpunkt im booleschen Operator ||

    – Slava

    8. September 2014 um 14:09 Uhr

Benutzer-Avatar
ani627

In C kann der gespeicherte Wert eines Objekts nur einmal zwischen zwei Sequenzpunkten modifiziert werden.

Ein Sequenzpunkt tritt auf:

  1. Am Ende des vollen Ausdrucks.
  2. Bei der &&, || und ?: Betreiber
  3. Bei einem Funktionsaufruf.

Also zum Beispiel dieser Ausdruck x = i++ * i++ ist nicht definiertwohingegen x = i++ && i++ ist vollkommen legal.

Ihr Code wird angezeigt definiertes Verhalten.

Ganzzahl x=0;

cout << ( x == 0 ? x++ : x) << endl;

Im obigen Ausdruck x ist 0Also x++ wird ausgeführt, hier ist x++ Post-Inkrement, also wird es ausgegeben 0.

cout << "x=" << x << endl;

Im obigen Ausdruck als x hat jetzt einen Wert 1 so wird die Ausgabe sein 1.

cout << (x == 1 || --x == 0 ? 1 : 2) << endl;

Hier x ist 1 also wird die nächste Bedingung nicht ausgewertet(--x == 0) und die Ausgabe wird 1.

cout << "x=" << x << endl;

Als Ausdruck --x == 0 nicht ausgewertet wird die Ausgabe wieder 1.

Benutzer-Avatar
Nikolaus Müller

Ja, es ist sicher, die Inkrement/Dekrement-Operatoren so zu verwenden, wie Sie es getan haben. Folgendes passiert in Ihrem Code:

Ausschnitt Nr. 1

cout << (x == 0 ? x++ : x) << endl; //operator in branch

In diesem Snippet testen Sie, ob x == 0welches ist true. Seit es ist truewertet Ihr ternärer Ausdruck die aus x++. Da Sie hier ein Post-Inkrement verwenden, wird der ursprüngliche Wert für x wird in den Standardausgabestrom gedruckt, und dann x wird erhöht.

Ausschnitt Nr. 2

cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

Dieses Snippet ist etwas verwirrender, liefert aber dennoch ein vorhersehbares Ergebnis. An dieser Stelle, x = 1 ab dem ersten Ausschnitt. Im ternären Ausdruck wird zuerst der Bedingungsteil ausgewertet; jedoch wegen Kurzschluss, die zweite Bedingung, --x == 0wird nie ausgewertet.

Für C++ die Operatoren || und && sind die kurzschließenden booleschen Operatoren für logisches ODER und logisches UND beziehungsweise. Wenn Sie diese Operatoren verwenden, werden Ihre Bedingungen überprüft (von links nach rechts), bis das Endergebnis bestimmt werden kann. Sobald das Ergebnis bestimmt ist, werden keine Bedingungen mehr überprüft.

Wenn Sie sich Codeausschnitt #2 ansehen, prüft Ihre erste Bedingung, ob x == 1. Da wertet deine erste Bedingung zu true und Sie das logische ODER verwenden, müssen Sie keine weiteren Bedingungen auswerten. Das bedeutet, dass --x == 0 ist nie hingerichtet.


Eine kurze Randnotiz zum Kurzschließen:

Das Kurzschließen ist nützlich, um die Leistung Ihres Programms zu steigern. Angenommen, Sie hätten eine Bedingung wie diese, die mehrere zeitaufwändige Funktionen aufruft:

if (task1() && task2())
{ 
    //...Do something...
}

In diesem Beispiel task2 sollte niemals aufgerufen werden, es sei denn task1 erfolgreich abgeschlossen (task2 hängt von einigen Daten ab, die durch geändert werden task1).

Da wir eine verwenden kurzschließender UND-Operatorwenn task1 schlägt durch Rückkehr fehl false, dann verfügt die if-Anweisung über ausreichende Informationen, um vorzeitig zu beenden und die Überprüfung anderer Bedingungen zu beenden. Das bedeutet, dass task2 wird nie angerufen.

1365260cookie-checkIst es sicher, Inkrement-/Dekrementoperatoren in ternäre/bedingte Operatoren einzufügen?

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

Privacy policy