Dans Bjarne Stroustrup's The C ++ Programming Language 4th edition section 36.3.6
STL-like Operations, le code suivant est utilisé comme exemple de chaînage :
void f2()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
.replace( s.find( " don't" ), 6, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
L'assertion échoue gcc
(le voir en direct ) et Visual Studio
(le voir en direct ), mais il n'échoue pas lors de l'utilisation de Clang (le voir en direct ).
Pourquoi ai-je des résultats différents? L'un de ces compilateurs évalue-t-il incorrectement l'expression de chaînage ou ce code présente-t-il une forme de comportement non spécifié ou non défini ?
c++
c++11
language-lawyer
operator-precedence
unspecified-behavior
Shafik Yaghmour
la source
la source
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
cout << a << b << c
≡operator<<(operator<<(operator<<(cout, a), b), c)
est à peine moins moche.Réponses:
Le code présente un comportement non spécifié en raison d'un ordre d'évaluation non spécifié des sous-expressions bien qu'il n'invoque pas un comportement indéfini puisque tous les effets secondaires sont effectués dans des fonctions qui introduisent une relation de séquençage entre les effets secondaires dans ce cas.
Cet exemple est mentionné dans la proposition N4228: Refining Expression Evaluation Order for Idiomatic C ++ qui dit ce qui suit à propos du code dans la question:
Détails
Il peut être évident pour beaucoup que les arguments des fonctions ont un ordre d'évaluation non spécifié, mais il n'est probablement pas aussi évident comment ce comportement interagit avec les appels de fonctions chaînées. Ce n'était pas évident pour moi lorsque j'ai analysé ce cas pour la première fois et apparemment pas non plus pour tous les examinateurs experts .
À première vue, il peut sembler que puisque chacun
replace
doit être évalué de gauche à droite, les groupes d'arguments de fonction correspondants doivent également être évalués comme des groupes de gauche à droite.Ceci est incorrect, les arguments de fonction ont un ordre d'évaluation non spécifié, bien que le chaînage des appels de fonction introduise un ordre d'évaluation de gauche à droite pour chaque appel de fonction, les arguments de chaque appel de fonction ne sont séquencés qu'avant par rapport à l'appel de fonction membre dont ils font partie de. En particulier, cela affecte les appels suivants:
et:
qui sont séquencés de manière indéterminée par rapport à:
les deux
find
appels pourraient être évalués avant ou après lereplace
, ce qui importe car il a un effet secondaire surs
d'une manière qui modifierait le résultat defind
, il change la longueur des
. Donc, en fonction du moment où celareplace
est évalué par rapport aux deuxfind
appels, le résultat sera différent.Si nous regardons l'expression de chaînage et examinons l'ordre d'évaluation de certaines des sous-expressions:
et:
Notez que nous ignorons le fait que
4
et7
peut être divisé en plusieurs sous-expressions. Alors:A
est séquencé avantB
lequel est séquencé avantC
lequel est séquencé avantD
1
à9
être séquencés de manière indéterminée par rapport à d'autres sous-expressions avec certaines des exceptions énumérées ci-dessous1
à3
être séquencé avantB
4
à6
être séquencé avantC
7
à9
être séquencé avantD
La clé de ce problème est que:
4
à9
être séquencés de manière indéterminée par rapport àB
L'ordre potentiel de choix d'évaluation pour
4
et7
par rapport àB
explique la différence de résultats entreclang
etgcc
lors de l'évaluationf2()
. Dans mes tests,clang
évalueB
avant d'évaluer4
et7
alors que l'gcc
évalue après. Nous pouvons utiliser le programme de test suivant pour démontrer ce qui se passe dans chaque cas:Résultat pour
gcc
( voir en direct )Résultat pour
clang
( voir en direct ):Résultat pour
Visual Studio
( voir en direct ):Détails de la norme
Nous savons qu'à moins d'être spécifiés, les évaluations des sous-expressions ne sont pas séquencées, cela provient du projet de section standard C ++ 11
1.9
Exécution du programme qui dit:et nous savons qu'un appel de fonction introduit une relation avant séquencée des appels de fonction postfix expression et arguments par rapport au corps de la fonction, à partir de la section
1.9
:Nous savons également que l'accès aux membres de la classe et donc le chaînage seront évalués de gauche à droite, à partir de la section
5.2.5
Accès aux membres de la classe qui dit:Notez que dans le cas où l' expression-id finit par être une fonction membre non statique, elle ne spécifie pas l'ordre d'évaluation de la liste d'expressions dans le
()
car il s'agit d'une sous-expression séparée. La grammaire pertinente des5.2
expressions Postfix :Changements C ++ 17
La proposition p0145r3: Refining Expression Evaluation Order for Idiomatic C ++ a apporté plusieurs modifications. Y compris les changements qui donnent au code un comportement bien spécifié en renforçant l'ordre des règles d'évaluation pour les expressions postfixes et leur liste d'expressions .
[expr.call] p5 dit:
la source
"even"
,"don't"
et plusieurs instances des
sont non séquencée par rapport à l'autre.foo().func( bar() )
, il peut appelerfoo()
avant ou après l'appelbar()
. L' expression de suffixe estfoo().func
. Les arguments et l'expression de suffixe sont séquencés avant le corps defunc()
, mais sans séquence l'un par rapport à l'autre.Ceci est destiné à ajouter des informations sur le sujet en ce qui concerne C ++ 17. La proposition ( Refining Expression Evaluation Order for Idiomatic C ++ Revision 2 ) pour
C++17
résoudre le problème citant le code ci-dessus était un spécimen.Comme suggéré, j'ai ajouté des informations pertinentes de la proposition et de citer (met en évidence le mien):
Le document suggérait de changer la
C++17
règle sur l'ordre d'évaluation des expressions qui a été influencée parC
et qui existe depuis plus de trois décennies. Il a proposé que le langage garantisse des expressions idiomatiques contemporaines ou risque «des pièges et des sources de bogues obscurs et difficiles à trouver» , comme ce qui s'est passé avec le spécimen de code ci-dessus.Il est proposé
C++17
d' exiger que chaque expression ait un ordre d'évaluation bien défini :Le code ci-dessus se compile avec succès en utilisant
GCC 7.1.1
etClang 4.0.0
.la source