Je lisais sur l' ordre des violations d'évaluation , et ils donnent un exemple qui me laisse perplexe.
1) Si un effet secondaire sur un objet scalaire n'est pas séquencé par rapport à un autre effet secondaire sur le même objet scalaire, le comportement n'est pas défini.
// snip f(i = -1, i = -1); // undefined behavior
Dans ce contexte, i
est un objet scalaire , ce qui signifie apparemment
Les types arithmétiques (3.9.1), les types d'énumération, les types pointeur, le pointeur sur les types membres (3.9.2), std :: nullptr_t et les versions qualifiées cv de ces types (3.9.3) sont collectivement appelés types scalaires.
Je ne vois pas en quoi la déclaration est ambiguë dans ce cas. Il me semble que peu importe si le premier ou le deuxième argument est évalué en premier, i
finit par -1
, et les deux arguments le sont également -1
.
Quelqu'un peut-il clarifier?
METTRE À JOUR
J'apprécie vraiment toute la discussion. Jusqu'à présent, j'aime beaucoup la réponse de @ harmic car elle expose les pièges et les subtilités de la définition de cette déclaration malgré sa simplicité à première vue. @ acheong87 souligne certains problèmes qui surviennent lors de l'utilisation de références, mais je pense que c'est orthogonal à l'aspect des effets secondaires non séquencés de cette question.
RÉSUMÉ
Étant donné que cette question a retenu l'attention, je vais résumer les principaux points / réponses. Tout d'abord, permettez-moi de faire une petite digression pour souligner que "pourquoi" peut avoir des significations étroitement liées mais subtilement différentes, à savoir "pour quelle cause ", "pour quelle raison " et "dans quel but ". Je vais regrouper les réponses par lesquelles de ces significations de «pourquoi» elles ont abordées.
pour quelle cause
La réponse principale ici vient de Paul Draper , avec Martin J apportant une réponse similaire mais pas aussi complète. La réponse de Paul Draper se résume à
Il s'agit d'un comportement indéfini car il n'est pas défini quel est le comportement.
La réponse est globalement très bonne pour expliquer ce que dit la norme C ++. Il aborde également certains cas connexes d'UB tels que f(++i, ++i);
et f(i=1, i=-1);
. Dans le premier des cas connexes, il n'est pas clair si le premier argument doit être i+1
et le second i+2
ou vice versa; dans le second, il n'est pas clair si i
doit être 1 ou -1 après l'appel de fonction. Ces deux cas sont UB car ils relèvent de la règle suivante:
Si un effet secondaire sur un objet scalaire n'est pas séquencé par rapport à un autre effet secondaire sur le même objet scalaire, le comportement n'est pas défini.
Par conséquent, f(i=-1, i=-1)
est également UB car il relève de la même règle, malgré que l'intention du programmeur soit (à mon humble avis) évidente et sans ambiguïté.
Paul Draper précise également dans sa conclusion que
Serait-ce un comportement défini? Oui. At-il été défini? Non.
ce qui nous amène à la question de "pour quelle raison / but a été f(i=-1, i=-1)
laissé comme comportement indéfini?"
pour quelle raison / but
Bien qu'il y ait quelques oublis (peut-être négligents) dans la norme C ++, de nombreuses omissions sont bien motivées et servent un objectif spécifique. Bien que je sois conscient que le but est souvent soit de "faciliter le travail du compilateur-rédacteur", soit de "code plus rapide", j'étais surtout intéressé de savoir s'il y avait une bonne raison de quitter f(i=-1, i=-1)
UB.
harmic et supercat fournissent les principales réponses qui fournissent une raison pour l'UB. Harmic souligne qu'un compilateur d'optimisation qui pourrait diviser les opérations d'affectation apparemment atomiques en plusieurs instructions machine, et qu'il pourrait encore entrelacer ces instructions pour une vitesse optimale. Cela pourrait conduire à des résultats très surprenants: i
finit comme -2 dans son scénario! Ainsi, harmic montre comment l'attribution de la même valeur à une variable plus d'une fois peut avoir des effets néfastes si les opérations ne sont pas séquencées.
supercat fournit une exposition connexe des embûches d'essayer f(i=-1, i=-1)
de faire ce qu'il semble devoir faire. Il souligne que sur certaines architectures, il existe des restrictions strictes contre plusieurs écritures simultanées vers la même adresse mémoire. Un compilateur pourrait avoir du mal à comprendre cela si nous avions affaire à quelque chose de moins trivial que f(i=-1, i=-1)
.
davidf fournit également un exemple d'instructions d'entrelacement très similaires à celles d'Harmic.
Bien que chacun des exemples de Harmic, Supercat et Davidf soit quelque peu artificiel, pris ensemble, ils servent toujours à fournir une raison tangible pour laquelle f(i=-1, i=-1)
un comportement devrait être indéfini.
J'ai accepté la réponse d'Harmic parce qu'elle a fait le meilleur travail pour aborder toutes les significations du pourquoi, même si la réponse de Paul Draper abordait mieux la partie "pour quelle cause".
autres réponses
JohnB souligne que si nous considérons des opérateurs d'affectation surchargés (au lieu de simples scalaires), nous pouvons également rencontrer des problèmes.
f(i-1, i = -1)
ou quelque chose de similaire.
std::nullptr_t
et les versions qualifiées cv de ces types (3.9.3) sont collectivement appelés types scalaires . "