c++20 reflection



Ce qui fait i=i+++1; légal en C++ 17? (2)

Dans les normes C ++ plus anciennes et dans C11, la définition du texte de l'opérateur d'assignation se termine par le texte suivant:

Les évaluations des opérandes ne sont pas séquencées.

Cela signifie que les effets secondaires dans les opérandes ne sont pas séquencés et qu’ils ont donc définitivement un comportement indéfini s’ils utilisent la même variable.

Ce texte a simplement été supprimé de C ++ 11, le laissant quelque peu ambigu. Est-ce UB ou non? Cela a été clarifié dans C ++ 17 où ils ont ajouté:

L'opérande droit est séquencé avant l'opérande gauche.

En remarque, dans des normes encore plus anciennes, tout cela était très clair, exemple de C99:

L'ordre d'évaluation des opérandes est indéterminé. Si vous tentez de modifier le résultat d'un opérateur d'affectation ou d'y accéder après le point de séquence suivant, le comportement n'est pas défini.

Fondamentalement, dans C11 / C ++ 11, ils ont tout gâché en supprimant ce texte.

Avant de commencer à crier un comportement non dé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 , 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 la valeur du résultat de l'opérateur. Si un effet secondaire sur un emplacement de mémoire n'est pas séquencé par rapport à un autre effet secondaire sur le même emplacement de mémoire ou à un calcul de valeur utilisant la valeur d'un objet du même emplacement de mémoire et s'ils ne sont pas potentiellement concurrents, le comportement est indéfini.

valeur est définie sur [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.

À partir de [N3337 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 la 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 est indéfini.

De même, la valeur est définie à [N3337 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 la mise en oeuvre.

Ils sont identiques sauf la mention de la simultanéité qui importe peu, et avec l’utilisation de l’ emplacement mémoire au lieu d’ objet scalaire , où

Les types arithmétiques, types d'énumération, types de pointeur, pointeur sur les types de membre, std::nullptr_t et les versions cv-qualifiées de ces types sont collectivement appelés types scalaires.

Ce qui n'affecte pas l'exemple.

À partir de [N4659 expr.ass]

L'opérateur d'affectation (=) et les opérateurs d'affectation composés regroupent tous les groupes 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'attribution 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.

À partir de [N3337 expr.ass]

L'opérateur d'affectation (=) et les opérateurs d'affectation composés regroupent tous les groupes 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'attribution 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 manquante dans N3337.

La dernière phrase ne devrait cependant pas avoir d’importance, car l’opérande de gauche i n’est ni "un autre effet secondaire" ni "utilisant la valeur du même objet scalaire", car id-expression est une lvalue.


Answer #1

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 de droite. Notez qu'il s'agit d'une garantie relativement "faible": elle produit un séquençage uniquement en relation avec le calcul de la valeur de la RHS. Il ne dit rien sur les effets secondaires qui pourraient être présents dans l'ERS, car la survenue d'effets secondaires ne fait pas partie du calcul de la valeur . Les exigences de C ++ 11 n'établissent pas de séquence relative entre l'acte d'attribution et les effets secondaires éventuels de la RHS. C'est ce qui crée le potentiel pour UB.

Dans ce cas, le seul espoir réside dans les éventuelles garanties supplémentaires fournies 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 préfixe de ++ auraient permis de gagner du temps dans cet exemple. Mais postfix ++ est une autre histoire: il ne fait pas de telles garanties. En C ++ 11, les effets secondaires de = et postfix ++ se retrouvent sans ordre dans leur relation 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:

L'opérande droit est séquencé avant l'opérande gauche.

En combinaison avec ce qui précède, il constitue une garantie très solide. Il séquence tout ce qui se passe dans la RHS (y compris tous les effets secondaires) avant tout ce qui se passe dans la LHS. Étant donné que l’assignation réelle est séquencée après LHS (et RHS), cette séquence supplémentaire isole complètement l’acte d’assignation de tout effet secondaire présent dans RHS. Cette séquence plus forte est ce qui élimine le UB ci-dessus.

(Mise à jour pour prendre en compte les commentaires de @John Bollinger.)





c++17