Après avoir lu cette réponse à propos de comportement indéfini et de 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 le décrément venir! Que se passe-t-il ici?
En outre, 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 de ma première question.
Dans l'expression (i, ++i, 1)
, la virgule utilisée est le opérateur de 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).
Puisqu'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 sur le premier opérande n'a pas lieu, le compilateur peut alors générer un avertissement concernant l'expression sans effet.
Ainsi, dans l'expression ci-dessus, le plus à gauche i
sera évalué et sa valeur sera ignorée. Ensuite ++i
sera évalué et incrémentera i
de 1, puis la valeur de l'expression ++i
sera jeté, mais l’effet secondaire de i
est permanent. Ensuite 1
sera é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 de comportement indéfini car il y a un point de séquence entre l'évaluation de la opérandes gauche et droite de l'opérateur virgule.
Citant de C11
, chapitre 6.5.17
, Opérateur de virgule
L'opérande gauche d'un opérateur de virgule est évalué en tant qu'expression vide. il y a un point de séquence entre son évaluation et celle de l'opérande droit. Ensuite, l'opérande de droite est évalué. le résultat a son type et sa valeur.
Donc, dans votre cas,
(i, ++i, 1)
est évalué comme
i
, est évalué comme une expression vide, valeur ignorée++i
, est évalué comme une expression vide, valeur ignorée1
, valeur retournée.Ainsi, la déclaration finale ressemble à
i = 1 + 1;
et i
arrive à 2
. Je suppose que cela répond à vos deux questions,
i
obtient-il une valeur 2?Remarque: FWIW, car il existe un point de séquence présent après l'évaluation de l'opérande de gauche, une expression comme (i, ++i, 1)
ne fera pas appel à UB, car on peut pense généralement par erreur.
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 dernière tâche maintenant:
i = 2;
Tout ce qui était dans i avant qu'il ne soit écrasé maintenant.
Le résultat de
(i, ++i, 1)
est
1
For
(i,++i,1)
l'évaluation se fait de telle sorte que le ,
L’opérateur jette la valeur évaluée et conserve la valeur la plus à droite, à savoir 1
Alors
i = 1 + 1 = 2
Vous trouverez une bonne lecture sur la page wiki pour le Opérateur de 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 son type).
Cela signifie que
(i, i++, 1)
à son tour évaluera i
, écartera le résultat, évaluera i++
, ignore le résultat, puis évalue et renvoie 1
.
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é et la troisième expression, 1
, est retourné pour toute l'expression.
Le résultat est donc: i = 1 + 1
.
Pour votre question bonus, comme vous le voyez, la première expression i
n'a aucun effet, aussi le compilateur se plaint-il.
La virgule a une priorité "inverse". C'est ce que vous obtiendrez d'anciens livres et de manuels C d'IBM (années 70/80). Donc, la dernière "commande" correspond à ce qui est utilisé dans l'expression parent.
En C moderne, son utilisation est étrange mais très intéressante en vieux C (ANSI):
do {
/* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);
Bien que toutes les opérations (fonctions) soient appelées de gauche à droite, seule la dernière expression sera utilisée comme résultat du conditionnel 'while'. Cela empêche la manipulation des commandes pour conserver un bloc unique de commandes à exécuter avant la vérification des conditions.
EDIT: Ceci évite également un appel à une fonction de traitement qui pourrait s’occuper de toute la logique des opérandes à gauche et renverrait ainsi le résultat logique. Rappelez-vous que nous n'avions pas de fonction inline dans le passé de C. Cela pouvait donc éviter un temps système supplémentaire.