Que signifie i = (i, ++ i, 1) + 1; faire?

174

Après avoir lu cette réponse sur le comportement indéfini et les points de séquence, j'ai écrit un petit programme:

#include <stdio.h>

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

La sortie est 2. Oh mon Dieu, je n'ai pas vu venir le décrément! Que se passe-t-il ici?

De plus, lors de la compilation du code ci-dessus, j'ai reçu un avertissement disant:

px.c: 5: 8: avertissement: l'opérande gauche de l'expression virgule n'a aucun effet

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

Pourquoi? Mais il sera probablement répondu automatiquement par la réponse à ma première question.

gsamaras
la source
289
Ne faites pas de choses bizarres, vous n'aurez pas d'amis :(
Maroun
9
Le message d'avertissement est la réponse à votre première question.
Yu Hao
2
@gsamaras: non. la valeur résultante est ignorée, pas la modification. la vraie réponse: l'opérateur virgule crée un point de séquence.
Karoly Horvath
3
@gsamaras Vous ne devriez pas vous soucier quand vous avez un score positif et encore plus avec 10+ question.
LyingOnTheSky
9
Remarque: Un compilateur d'optimisation peut faire simpleprintf("2\n");
chux - Réinstallez Monica le

Réponses:

256

Dans l'expression (i, ++i, 1), la virgule utilisée est l' opérateur virgule

l'opérateur virgule (représenté par le jeton ,) est un opérateur binaire qui évalue son premier opérande et ignore le résultat, puis évalue le deuxième opérande et renvoie cette valeur (et ce type).

Parce qu'il rejette son premier opérande, il n'est généralement utile que lorsque le premier opérande a des effets secondaires souhaitables . Si l'effet secondaire du premier opérande ne se produit pas, le compilateur peut générer un avertissement sur l'expression sans effet.

Ainsi, dans l'expression ci-dessus, l'extrême gauche isera évalué et sa valeur sera ignorée. Ensuite ++isera évalué et incrémenté ide 1 et à nouveau la valeur de l'expression ++isera ignorée, mais l'effet secondaire iest permanent . Ensuite 1sera évalué et la valeur de l'expression sera 1.

C'est équivalent à

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;  

Notez que l'expression ci-dessus est parfaitement valide et n'invoque pas un comportement indéfini car il existe un point de séquence entre l'évaluation des opérandes gauche et droit de l'opérateur virgule.

piratages
la source
1
bien que l'expression finale soit valide, la deuxième expression ++ i n'est-elle pas un comportement indéfini? il est évalué et la valeur de la variable non initialisée est pré-incrémentée, ce qui n'est pas correct? ou est-ce que je manque quelque chose?
Koushik Shetty
2
@Koushik; iest initialisé avec 5. Regardez la déclaration de déclaration int i = 5;.
haccks le
1
oh mon mal. Désolé je n'ai honnêtement pas vu ça.
Koushik Shetty le
Il y a une erreur ici: ++ je vais incrémenter i puis l'évaluer, tandis que i ++ évaluera i puis l'incrémentera.
Quentin Hayot le
1
@QuentinHayot; Quoi? Tout effet secondaire survient après l'évaluation de l'expression. Dans le cas de ++i, cette expression sera évaluée, isera incrémentée et cette valeur incrémentée sera la valeur de l'expression. Dans le cas de i++, cette expression sera évaluée, l'ancienne valeur de isera la valeur de l'expression, isera incrémentée à tout moment entre le point de séquence précédent et suivant de l'expression.
haccks le
62

Citant de C11, chapitre 6.5.17, opérateur virgule

L'opérande gauche d'un opérateur virgule est évalué comme une expression vide; il y a un point de séquence entre son évaluation et celle de l'opérande droit. Ensuite, l'opérande droit est évalué; le résultat a son type et sa valeur.

Donc, dans votre cas,

(i, ++i, 1)

est évalué comme

  1. i, est évalué comme une expression vide, valeur ignorée
  2. ++i, est évalué comme une expression vide, valeur ignorée
  3. enfin, 1valeur retournée.

Donc, la déclaration finale ressemble à

i = 1 + 1;

et iarrive à 2. Je suppose que cela répond à vos deux questions,

  • Comment iobtient une valeur 2?
  • Pourquoi y a-t-il un message d'avertissement?

Remarque: FWIW, comme il y a un point de séquence présent après l'évaluation de l'opérande de gauche, une expression comme (i, ++i, 1)n'invoquera pas UB, comme on peut généralement le penser par erreur.

Sourav Ghosh
la source
+1 Sourav, puisque cela explique pourquoi l'initialisation de in'a clairement aucun effet! Cependant, je ne pense pas que ce soit si évident pour un gars qui ne connaît pas l'opérateur virgule (et je ne savais pas comment chercher de l'aide, à part poser une question). Dommage que j'ai eu tellement de votes négatifs! Je vais vérifier les autres réponses, puis décider lesquelles accepter. Merci! Belle réponse top btw.
gsamaras
Je sens que je dois expliquer pourquoi j'ai accepté la réponse de haccks. J'étais prêt à accepter la vôtre, car elle répond vraiment à mes deux questions. Cependant, si vous vérifiez les commentaires de ma question, vous verrez que certaines personnes ne peuvent pas voir à première vue pourquoi cela n'invoque pas UB. Les réponses de haccks fournissent des informations pertinentes. Bien sûr, j'ai la réponse concernant UB liée à ma question, mais certaines personnes peuvent manquer cela. J'espère que vous êtes d'accord avec ma décision, sinon faites-le moi savoir. :)
gsamaras
30
i = (i, ++i, 1) + 1;

Analysons-le étape par étape.

(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

Nous obtenons donc 2. Et la mission finale maintenant:

i = 2;

Tout ce qui était dans i avant il est écrasé maintenant.

dlask
la source
Ce serait bien de dire que cela se produit à cause de l'opérateur virgule. +1 pour l'analyse étape par étape! Belle réponse top btw.
gsamaras
Je suis désolé pour une explication insuffisante, je n'ai qu'une note là-bas ( ... mais ignorée, il y en a ... ). Je voulais expliquer principalement pourquoi le ++ine contribue pas au résultat.
dlask
maintenant mes boucles for seront toujours commeint i = 0; for( ;(++i, i<max); )
CoffeDeveloper
19

Le résultat de

(i, ++i, 1)

est

1

Pour

(i,++i,1) 

l'évaluation se produit de telle sorte que l' ,opérateur rejette la valeur évaluée et retiendra juste la valeur la plus juste qui est1

Alors

i = 1 + 1 = 2
Gopi
la source
1
Ouais j'ai pensé à ça aussi, mais je ne sais pas pourquoi!
gsamaras
@gsamaras parce que l'opérateur virgule évalue le terme précédent mais le rejette (c'est-à-dire qu'il ne l'utilise pas pour les affectations ou autres)
Marco A.
14

Vous trouverez de bonnes lectures sur la page wiki pour l' opérateur Virgule .

Fondamentalement, il

... évalue son premier opérande et ignore le résultat, puis évalue le deuxième opérande et renvoie cette valeur (et ce type).

Cela signifie que

(i, i++, 1)

va, à son tour, évaluer i, rejeter le résultat, évaluer i++, rejeter le résultat, puis évaluer et retourner 1.

Tomas Aschan
la source
O_O enfer, cette syntaxe est-elle valide en C ++, je me souviens que j'avais peu d'endroits où j'avais besoin de cette syntaxe (en gros, j'ai écrit: (void)exp; a= exp2;alors que j'en avais juste besoin a = exp, exp2;)
CoffeDeveloper
13

Vous devez savoir ce que fait l'opérateur virgule ici:

Votre expression:

(i, ++i, 1)

La première expression i,, est évaluée, la deuxième expression ++i,, est évaluée et la troisième expression,, 1est renvoyée pour toute l'expression.

Le résultat est donc: i = 1 + 1.

Pour votre question bonus, comme vous le voyez, la première expression in'a aucun effet, donc le compilateur se plaint.

songyuanyao
la source
5

La virgule a une priorité «inverse». C'est ce que vous obtiendrez des vieux livres et manuels C d'IBM (années 70/80). Donc, la dernière «commande» est ce qui est utilisé dans l'expression parent.

En C moderne, son utilisation est étrange mais très intéressante dans l'ancien C (ANSI):

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

Alors que toutes les opérations (fonctions) sont appelées de gauche à droite, seule la dernière expression sera utilisée comme résultat du conditionnel «while». Cela empêche la gestion de 'goto's pour conserver un bloc unique de commandes à exécuter avant la vérification de la condition.

EDIT: Cela évite également un appel à une fonction de gestion qui pourrait prendre en charge toute la logique aux opérandes de gauche et donc renvoyer le résultat logique. Rappelez-vous que nous n'avions pas de fonction en ligne dans le passé de C. Donc, cela pourrait éviter une surcharge d'appel.

Luciano
la source
Luciano, vous avez également un lien vers cette réponse: stackoverflow.com/questions/17902992/… .
gsamaras du
Au début des années 90 avant les fonctions en ligne, je l'utilisais beaucoup pour optimiser et organiser le code.
Luciano du