Avant de commencer à crier un comportement indéfini, cela est explicitement répertorié dans N4659 (C ++ 17)
i = i++ + 1; // the value of i is incremented
Pourtant dans N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
Qu'est ce qui a changé?
D'après ce que je peux comprendre , à partir de [N4659 basic.exec]
Sauf indication contraire, les évaluations d'opérandes d'opérateurs individuels et de sous-expressions d'expressions individuelles ne sont pas séquencées. [...] Les calculs de valeur des opérandes d'un opérateur sont séquencés avant le calcul de valeur du résultat de l'opérateur. Si un effet secondaire sur un emplacement mémoire n'est pas séquencé par rapport à un autre effet secondaire sur le même emplacement mémoire ou à un calcul de valeur utilisant la valeur de n'importe quel objet dans le même emplacement mémoire, et qu'ils ne sont pas potentiellement simultanés, le comportement n'est pas défini.
Où la valeur est définie dans [N4659 basic.type]
Pour les types trivialement copiables, la représentation de valeur est un ensemble de bits dans la représentation d'objet qui détermine une valeur , qui est un élément discret d'un ensemble de valeurs défini par l'implémentation
Sauf indication contraire, les évaluations d'opérandes d'opérateurs individuels et de sous-expressions d'expressions individuelles ne sont pas séquencées. [...] Les calculs de valeur des opérandes d'un opérateur sont séquencés avant le calcul de valeur du résultat de l'opérateur. 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 ou à un calcul de valeur utilisant la valeur du même objet scalaire, le comportement n'est pas défini.
De même, la valeur est définie dans [N3337 basic.type]
Pour les types facilement copiables, la représentation de valeur est un ensemble de bits dans la représentation d'objet qui détermine une valeur , qui est un élément discret d'un ensemble de valeurs défini par l'implémentation.
Ils sont identiques sauf mention de la concurrence qui n'a pas d'importance, et avec l'utilisation de l' emplacement mémoire au lieu de l' objet scalaire , où
Les types arithmétiques, les types d'énumération, les types de pointeur, le pointeur vers les types de membre
std::nullptr_t
et les versions qualifiées cv de ces types sont collectivement appelés types scalaires.
Ce qui n'affecte pas l'exemple.
L'opérateur d'affectation (=) et les opérateurs d'affectation composés sont tous regroupés de droite à gauche. Tous nécessitent une lvalue modifiable comme opérande gauche et renvoient une lvalue faisant référence à l'opérande gauche. Le résultat dans tous les cas est un champ de bits si l'opérande de gauche est un champ de bits. Dans tous les cas, l'affectation est séquencée après le calcul de la valeur des opérandes droit et gauche et avant le calcul de la valeur de l'expression d'affectation. L'opérande droit est séquencé avant l'opérande gauche.
L'opérateur d'affectation (=) et les opérateurs d'affectation composés sont tous regroupés de droite à gauche. Tous nécessitent une lvalue modifiable comme opérande gauche et renvoient une lvalue faisant référence à l'opérande gauche. Le résultat dans tous les cas est un champ de bits si l'opérande de gauche est un champ de bits. Dans tous les cas, l'affectation est séquencée après le calcul de la valeur des opérandes droit et gauche et avant le calcul de la valeur de l'expression d'affectation.
La seule différence étant la dernière phrase étant absente de N3337.
Cependant, la dernière phrase ne devrait pas avoir d'importance car l'opérande de gauche i
n'est ni "un autre effet secondaire" ni "l'utilisation de la valeur du même objet scalaire" car l' expression id est une lvalue.
la source
i = i++ + 1;
.Réponses:
En C ++ 11, l'acte d '"affectation", c'est-à-dire l'effet secondaire de la modification du LHS, est séquencé après le calcul de la valeur de l'opérande droit. Notez qu'il s'agit d'une garantie relativement "faible": elle ne produit de séquençage que par rapport au calcul de valeur du RHS. Il ne dit rien sur les effets secondaires qui pourraient être présents dans le RHS, puisque l'apparition d'effets secondaires ne fait pas partie du calcul de la valeur . Les exigences de C ++ 11 n'établissent aucun séquencement relatif entre l'acte d'assignation et les effets secondaires du RHS. C'est ce qui crée le potentiel pour UB.
Le seul espoir dans ce cas est d'éventuelles garanties supplémentaires faites par des opérateurs spécifiques utilisés dans RHS. Si le RHS utilisait un préfixe
++
, les propriétés de séquençage spécifiques à la forme de préfixe++
auraient sauvé la journée dans cet exemple. Mais postfix++
est une autre histoire: il n'offre pas de telles garanties. Dans C ++ 11, les effets secondaires de=
et de postfix ne sont++
pas séquencés les uns par rapport aux autres dans cet exemple. Et c'est UB.En C ++ 17, une phrase supplémentaire est ajoutée à la spécification de l'opérateur d'affectation:
En combinaison avec ce qui précède, cela constitue une garantie très solide. Il séquence tout ce qui se passe dans le RHS (y compris les effets secondaires) avant tout ce qui se passe dans le LHS. Puisque l'attribution réelle est séquencée après LHS (et RHS), ce séquençage supplémentaire isole complètement l'acte d'assignation de tout effet secondaire présent dans RHS. Ce séquençage plus fort est ce qui élimine l'UB ci-dessus.
(Mis à jour pour prendre en compte les commentaires de @John Bollinger.)
la source
x -= y;
à traiter commemov eax,[y] / sub [x],eax
plutôt quemov eax,[x] / neg eax / add eax,[y] / mov [x],eax
. Je n'y vois rien d'idiotique. Si l'on devait spécifier un ordre, l'ordre le plus efficace serait probablement d'effectuer tous les calculs nécessaires pour identifier d' abord l'objet de gauche, puis d'évaluer l'opérande de droite, puis la valeur de l'objet de gauche, mais cela nécessiterait d'avoir un terme pour l'acte de résoudre l'identité de l'objet gauche.x
ety
sivolatile
, cela aurait des effets secondaires. En outre, les mêmes considérations s'appliqueraient àx += f();
, oùf()
modifiex
.Vous avez identifié la nouvelle phrase
et vous avez correctement identifié que l'évaluation de l'opérande de gauche en tant que valeur l n'est pas pertinente. Cependant, séquencé avant est spécifié comme étant une relation transitive. L'opérande droit complet (y compris le post-incrément) est donc également séquencé avant l'affectation. En C ++ 11, seul le calcul de la valeur de l'opérande droit était séquencé avant l'affectation.
la source
Dans les anciennes normes C ++ et en C11, la définition du texte de l'opérateur d'affectation se termine par le texte:
Cela signifie que les effets secondaires dans les opérandes ne sont pas séquencés et donc certainement un comportement indéfini s'ils utilisent la même variable.
Ce texte a simplement été supprimé dans C ++ 11, le laissant quelque peu ambigu. Est-ce UB ou non? Cela a été clarifié dans C ++ 17 où ils ont ajouté:
En remarque, dans des normes encore plus anciennes, tout cela était très clair, exemple de C99:
Fondamentalement, en C11 / C ++ 11, ils se sont trompés lorsqu'ils ont supprimé ce texte.
la source
Ceci est une information supplémentaire aux autres réponses et je la poste car le code ci-dessous est souvent également demandé .
L'explication dans les autres réponses est correcte et s'applique également au code suivant qui est maintenant bien défini (et ne change pas la valeur stockée de
i
):C'est
+ 1
un hareng rouge et on ne sait pas vraiment pourquoi le Standard l'a utilisé dans ses exemples, même si je me souviens que des gens se disputaient sur des listes de diffusion avant C ++ 11, cela a peut-être+ 1
fait une différence en forçant la conversion précoce de lvalue à droite- côté. Certainement rien de tout cela ne s'applique en C ++ 17 (et probablement jamais appliqué dans aucune version de C ++).la source