Was bedeutet i = (i, ++ i, 1) + 1; machen?

174

Nachdem ich diese Antwort über undefiniertes Verhalten und Sequenzpunkte gelesen hatte , schrieb ich ein kleines Programm:

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

Die Ausgabe ist 2. Oh Gott, ich habe das Dekrement nicht kommen sehen! Was passiert hier?

Außerdem habe ich beim Kompilieren des obigen Codes eine Warnung erhalten, die besagt:

px.c: 5: 8: Warnung: Der linke Operand des Kommaausdrucks hat keine Auswirkung

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

Warum? Aber wahrscheinlich wird es automatisch durch die Antwort auf meine erste Frage beantwortet.

gsamaras
quelle
289
Mach keine seltsamen Dinge, du wirst keine Freunde haben :(
Maroun
9
Die Warnmeldung ist die Antwort auf Ihre erste Frage.
Yu Hao
2
@ gsamaras: nein. Der resultierende Wert wird verworfen, nicht die Änderung. Die eigentliche Antwort: Der Komma-Operator erstellt einen Sequenzpunkt.
Karoly Horvath
3
@gsamaras Es sollte dir egal sein, wenn du eine positive Punktzahl hast und noch mehr mit 10+ Fragen.
LyingOnTheSky
9
Hinweis: Eine Optimierung der Compiler kann einfach tunprintf("2\n");
CHUX - wieder einzusetzen Monica

Antworten:

256

Im Ausdruck (i, ++i, 1)ist das verwendete Komma der Kommaoperator

Der Kommaoperator (dargestellt durch das Token ,) ist ein binärer Operator, der seinen ersten Operanden auswertet und das Ergebnis verwirft. Anschließend wertet er den zweiten Operanden aus und gibt diesen Wert (und Typ) zurück.

Da es seinen ersten Operanden verwirft, ist es im Allgemeinen nur dann nützlich, wenn der erste Operand wünschenswerte Nebenwirkungen hat . Wenn der Nebeneffekt zum ersten Operanden nicht auftritt, generiert der Compiler möglicherweise eine Warnung über den Ausdruck ohne Auswirkung.

Im obigen Ausdruck wird also das am weitesten links stehende iausgewertet und sein Wert verworfen. Dann ++iwird ausgewertet und ium 1 erhöht, und der Wert des Ausdrucks ++iwird erneut verworfen, aber die Nebenwirkung von iist dauerhaft . Dann 1wird ausgewertet und der Wert des Ausdrucks wird 1.

Es ist äquivalent zu

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

Beachten Sie, dass der obige Ausdruck vollkommen gültig ist und kein undefiniertes Verhalten hervorruft, da zwischen der Auswertung des linken und rechten Operanden des Kommaoperators ein Sequenzpunkt liegt .

Haccks
quelle
1
Obwohl der endgültige Ausdruck gültig ist, ist der zweite Ausdruck ++ i nicht ein undefiniertes Verhalten? es wird ausgewertet und der Wert der nicht initialisierten Variablen wird vorinkrementiert, was ist nicht richtig? oder fehlt mir etwas
Koushik Shetty
2
@Koushik; iwird mit initialisiert 5. Schauen Sie sich die Erklärung an int i = 5;.
Haccks
1
oh mein schlechtes. Tut mir leid, dass ich das ehrlich gesagt nicht sehe.
Koushik Shetty
Hier gibt es einen Fehler: ++ Ich werde es inkrementieren und dann auswerten, während i ++ es auswerten und dann inkrementieren wird.
Quentin Hayot
1
@QuentinHayot; Was? Alle Nebenwirkungen treten nach der Bewertung der Expression auf. In diesem Fall ++iwird dieser Ausdruck ausgewertet, iinkrementiert und dieser inkrementierte Wert ist der Wert des Ausdrucks. In diesem Fall i++wird dieser Ausdruck ausgewertet, der alte Wert von iist der Wert des Ausdrucks und iwird jederzeit zwischen dem vorherigen und dem nächsten Sequenzpunkt des Ausdrucks erhöht.
Haccks
62

Zitat aus C11Kapitel 6.5.17, Komma-Operator

Der linke Operand eines Kommaoperators wird als void-Ausdruck ausgewertet. Es gibt einen Sequenzpunkt zwischen seiner Auswertung und der des richtigen Operanden. Dann wird der richtige Operand ausgewertet; Das Ergebnis hat seinen Typ und Wert.

Also, in deinem Fall,

(i, ++i, 1)

wird bewertet als

  1. i, wird als ungültiger Ausdruck ausgewertet, Wert verworfen
  2. ++i, wird als ungültiger Ausdruck ausgewertet, Wert verworfen
  3. Schließlich 1wurde der Wert zurückgegeben.

Die endgültige Aussage sieht also so aus

i = 1 + 1;

und ikommt zu 2. Ich denke, dies beantwortet Ihre beiden Fragen.

  • Wie ibekommt man einen Wert 2?
  • Warum gibt es eine Warnmeldung?

Hinweis: FWIW, da nach der Auswertung des linken Operanden ein Sequenzpunkt vorhanden ist, ruft ein Ausdruck wie (i, ++i, 1)UB nicht auf, wie man im Allgemeinen versehentlich denken kann .

Sourav Ghosh
quelle
+1 Sourav, da dies erklärt, warum die Initialisierung von ieindeutig keine Wirkung hat! Ich glaube jedoch nicht, dass es für einen Mann so offensichtlich war, der den Kommaoperator nicht kennt (und ich wusste nicht, wie ich nach Hilfe suchen sollte, außer eine Frage zu stellen). Schade, dass ich so viele Downvotes bekommen habe! Ich werde die anderen Antworten überprüfen und dann entscheiden, welche zu akzeptieren sind. Vielen Dank! Schöne Top Antwort übrigens.
Gsamaras
Ich habe das Gefühl, ich muss erklären, warum ich die Antwort von Haccks akzeptiert habe. Ich war bereit, Ihre zu akzeptieren, da es meine beiden Fragen wirklich beantwortet. Wenn Sie jedoch die Kommentare meiner Frage überprüfen, werden Sie feststellen, dass einige Leute auf den ersten Blick nicht sehen können, warum dies UB nicht aufruft. Haccks Antworten liefern einige relevante Informationen. Natürlich habe ich die Antwort bezüglich UB in meiner Frage verlinkt, aber einige Leute mögen das vermissen. Ich hoffe, Sie stimmen meiner Entscheidung zu, wenn nicht, lassen Sie es mich wissen. :)
gsamaras
30
i = (i, ++i, 1) + 1;

Lassen Sie es uns Schritt für Schritt analysieren.

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

So erhalten wir 2. Und die endgültige Aufgabe jetzt:

i = 2;

Was auch immer in war ich , bevor es jetzt überschrieben.

dlask
quelle
Es wäre schön zu sagen, dass dies aufgrund des Kommaoperators geschieht. +1 für die Schritt-für-Schritt-Analyse! Schöne Top Antwort übrigens.
Gsamaras
Es tut mir leid für die unzureichende Erklärung, ich habe dort nur eine Notiz ( ... aber ignoriert, es gibt ... ). Ich wollte vor allem erklären, warum das ++inicht zum Ergebnis beiträgt.
dlask
Jetzt werden meine for-Schleifen immer so seinint i = 0; for( ;(++i, i<max); )
CoffeDeveloper
19

Das Ergebnis von

(i, ++i, 1)

ist

1

Zum

(i,++i,1) 

Die Auswertung erfolgt so, dass der ,Bediener den ausgewerteten Wert verwirft und genau den am weitesten rechts liegenden Wert beibehält1

So

i = 1 + 1 = 2
Gopi
quelle
1
Ja, daran habe ich auch gedacht, aber ich weiß nicht warum!
Gsamaras
@gsamaras, weil der Komma-Operator den vorherigen Begriff auswertet, ihn aber verwirft (dh ihn nicht für Aufgaben oder ähnliches verwendet)
Marco A.
14

Auf der Wiki-Seite finden Sie einige gute Informationen für den Komma-Operator .

Grundsätzlich ist es

... wertet seinen ersten Operanden aus und verwirft das Ergebnis, wertet dann den zweiten Operanden aus und gibt diesen Wert (und Typ) zurück.

Dies bedeutet, dass

(i, i++, 1)

wird seinerseits bewerten i, das Ergebnis verwerfen, bewerten i++, das Ergebnis verwerfen und dann bewerten und zurückgeben 1.

Tomas Aschan
quelle
O_O Hölle, ist diese Syntax in C ++ gültig? Ich erinnere mich, dass ich nur wenige Stellen hatte, an denen ich diese Syntax brauchte (im Grunde habe ich geschrieben: (void)exp; a= exp2;während ich nur brauchte a = exp, exp2;)
CoffeDeveloper
13

Sie müssen wissen, was der Kommaoperator hier tut:

Dein Ausdruck:

(i, ++i, 1)

Der erste Ausdruck iwird ausgewertet, der zweite Ausdruck ++iwird ausgewertet und der dritte Ausdruck 1wird für den gesamten Ausdruck zurückgegeben.

Das Ergebnis ist also : i = 1 + 1.

Wie Sie sehen, hat der erste Ausdruck für Ihre Bonusfrage iüberhaupt keine Wirkung, daher beschwert sich der Compiler.

songyuanyao
quelle
5

Komma hat eine "umgekehrte" Priorität. Dies erhalten Sie aus alten Büchern und C-Handbüchern von IBM (70er / 80er Jahre). Der letzte 'Befehl' wird also im übergeordneten Ausdruck verwendet.

Im modernen C ist seine Verwendung seltsam, aber im alten C (ANSI) sehr interessant:

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

Während alle Operationen (Funktionen) von links nach rechts aufgerufen werden, wird nur der letzte Ausdruck als Ergebnis der bedingten 'while' verwendet. Dies verhindert die Behandlung von 'goto', um einen eindeutigen Befehlsblock beizubehalten, der vor der Zustandsprüfung ausgeführt werden soll.

BEARBEITEN: Dadurch wird auch ein Aufruf einer Handling-Funktion vermieden, die sich um die gesamte Logik der linken Operanden kümmern und so das logische Ergebnis zurückgeben kann. Denken Sie daran, dass wir in der Vergangenheit von C keine Inline-Funktion hatten. Dies könnte also einen Anrufaufwand vermeiden.

Luciano
quelle
Luciano, du hast auch einen Link zu dieser Antwort: stackoverflow.com/questions/17902992/… .
Gsamaras
In den frühen 90ern vor Inline-Funktionen habe ich es häufig verwendet, um Code zu optimieren und zu organisieren.
Luciano