Récemment, dans une interview, il y avait une question de type objectif suivante.
int a = 0;
cout << a++ << a;
Réponses:
une. 10
b. 01
ch. comportement indéfini
J'ai répondu au choix b, c'est-à-dire que la sortie serait "01".
Mais à ma grande surprise plus tard, un intervieweur m'a dit que la bonne réponse était l'option c: indéfinie.
Maintenant, je connais le concept de points de séquence en C ++. Le comportement n'est pas défini pour l'instruction suivante:
int i = 0;
i += i++ + i++;
mais selon ma compréhension de la déclaration cout << a++ << a
, le ostream.operator<<()
serait appelé deux fois, d'abord avec ostream.operator<<(a++)
et plus tard ostream.operator<<(a)
.
J'ai également vérifié le résultat sur le compilateur VS2010 et sa sortie est également «01».
10
, ce serait soit01
ou00
. (c++
Toujours évaluer à la valeurc
avait avant d' être incrémentée). Et même si ce n'était pas indéfini, ce serait encore horriblement déroutant.Réponses:
Vous pouvez penser à:
Comme:
C ++ garantit que tous les effets secondaires des évaluations précédentes auront été exécutés aux points de séquence . Il n'y a pas de points de séquence entre l'évaluation des arguments de fonction, ce qui signifie que l'argument
a
peut être évalué avantstd::operator<<(std::cout, a++)
ou après l' argument . Donc, le résultat de ce qui précède n'est pas défini.Mise à jour C ++ 17
En C ++ 17, les règles ont été mises à jour. En particulier:
Ce qui signifie qu'il faut que le code produise un résultat
b
, qui sort01
.Voir P0145R3 Raffinage de l'ordre d'évaluation des expressions pour Idiomatic C ++ pour plus de détails.
la source
c
a typeint
, lesoperator<<
voici des fonctions membres.operator<<
une fonction membre ou d'une fonction autonome n'affecte pas les points de séquence.So the result of the above is undefined.
Votre explication n'est valable que pour non spécifié , pas pour indéfini . JamesKanze a expliqué comment c'était le plus indéfini dans sa réponse .Techniquement, dans l'ensemble, il s'agit d'un comportement indéfini .
Mais, il y a deux aspects importants à la réponse.
L'instruction de code:
est évalué comme:
La norme ne définit pas l'ordre d'évaluation des arguments d'une fonction.
Donc soit:
std::operator<<(std::cout, a++)
est évalué en premier oua
est évalué en premier ouCette commande n'est pas spécifiée [Ref 1] selon la norme.
[Ref 1] C ++ 03 5.2.2 Appel de fonction
Para 8
De plus, il n'y a pas de point de séquence entre l'évaluation des arguments d'une fonction mais un point de séquence n'existe qu'après évaluation de tous les arguments [Ref 2] .
[Ref 2] C ++ 03 1.9 Exécution du programme [intro.execution]:
Para 17:
Notez que, ici, la valeur de
c
est accédée plus d'une fois sans point de séquence intermédiaire, à ce sujet, la norme dit:[Réf 3] C ++ 03 5 Expressions [expr]:
Para 4:
Le code est modifié
c
plus d'une fois sans point de séquence intervenant et il n'est pas accédé pour déterminer la valeur de l'objet stocké. C'est une violation flagrante de la clause ci-dessus et par conséquent, le résultat tel que prescrit par la norme est un comportement indéfini [Réf 3] .la source
Les points de séquence ne définissent qu'un ordre partiel . Dans votre cas, vous avez (une fois la résolution de surcharge effectuée):
Il y a un point de séquence entre le
a++
et le premier appel àstd::ostream::operator<<
, et il y a un point de séquence entre le deuxièmea
et le deuxième appel àstd::ostream::operator<<
, mais il n'y a pas de point de séquence entrea++
eta
; les seules contraintes d'ordre sont celles qui doiventa++
être pleinement évaluées (y compris les effets secondaires) avant le premier appel àoperator<<
, et que la secondea
soit entièrement évaluée avant le deuxième appel àoperator<<
. (Il existe également des contraintes d'ordre causal: le deuxième appel àoperator<<
ne peut pas précéder le premier, car il nécessite les résultats du premier comme argument.) §5 / 4 (C ++ 03) déclare:L' une des ordonnancements admissibles de votre expression est
a++
,a
d'abord appel àoperator<<
, deuxième appel àoperator<<
; cela modifie la valeur stockée dea
(a++
), et y accède autrement que pour déterminer la nouvelle valeur (la secondea
), le comportement est indéfini.la source
c
avait un type d'utilisateur avec un utilisateur défini++
, au lieu deint
, les résultats ne seraient pas spécifiés, mais il n'y aurait pas de comportement indéfini.c
enfoo(foo(bar(c)), c)
? Il y a un point de séquence lorsque les fonctions sont appelées et quand elles reviennent, mais aucun appel de fonction n'est requis entre les évaluations des deuxc
.c
était un UDT, les opérateurs surchargés seraient des appels de fonction et introduiraient un point de séquence, de sorte que le comportement ne serait pas indéfini. Mais il ne serait toujours pas spécifié si la sous-expression ac
été évaluée avant ou aprèsc++
, donc si vous avez la version incrémentée ou non ne serait pas spécifié (et en théorie, ne devrait pas être la même à chaque fois).c
etc++
, donc les deux peuvent apparaître dans n'importe quel ordre. Quant aux points-virgules ... Ils ne provoquent un point de séquence que dans la mesure où ce sont des expressions complètes. D'autres points de séquence importants sont l'appel de fonction:f(c++)
verra l'incrémentéc
dansf
, et l'opérateur virgule&&
,||
et?:
provoquera également des points de séquence.La bonne réponse est de remettre en question la question. La déclaration est inacceptable car un lecteur ne peut pas voir une réponse claire. Une autre façon de voir les choses est que nous avons introduit des effets secondaires (c ++) qui rendent la déclaration beaucoup plus difficile à interpréter. Un code concis est excellent, à condition que sa signification soit claire.
la source