Der Code zeigt aufgrund der nicht spezifizierten Reihenfolge der Auswertung von Unterausdrücken ein nicht spezifiziertes Verhalten, obwohl er kein undefiniertes Verhalten hervorruft, da alle Nebenwirkungen innerhalb von Funktionen ausgeführt werden, was in diesem Fall eine Sequenzierungsbeziehung zwischen den Nebenwirkungen einführt .
Dieses Beispiel wird im Vorschlag N4228 erwähnt: Verfeinern der Ausdrucksauswertungsreihenfolge für idiomatisches C ++ , in dem Folgendes zum Code in der Frage angegeben wird:
[...] Dieser Code wird von C ++ Experten weltweit und veröffentlicht (die Programmiersprache C ++, 4 prüft th Edition.) Doch seine Anfälligkeit für nicht spezifizierte Reihenfolge der Auswertung wurde durch ein Werkzeug erst vor kurzem entdeckt [.. .]
Einzelheiten
Für viele mag es offensichtlich sein, dass Argumente für Funktionen eine nicht spezifizierte Bewertungsreihenfolge haben, aber es ist wahrscheinlich nicht so offensichtlich, wie dieses Verhalten mit verketteten Funktionsaufrufen interagiert. Es war mir nicht klar, als ich diesen Fall zum ersten Mal analysierte, und anscheinend auch nicht allen Experten .
Auf den ersten Blick mag es so aussehen, replace
als müssten die entsprechenden Funktionsargumentgruppen auch als Gruppen von links nach rechts ausgewertet werden , da jede von links nach rechts ausgewertet werden muss.
Dies ist falsch, Funktionsargumente haben eine nicht spezifizierte Auswertungsreihenfolge, obwohl das Verketten von Funktionsaufrufen eine Auswertungsreihenfolge von links nach rechts für jeden Funktionsaufruf einführt. Die Argumente jedes Funktionsaufrufs werden nur in Bezug auf den Elementfunktionsaufruf, zu dem sie gehören, vorher sequenziert von. Dies wirkt sich insbesondere auf folgende Aufrufe aus:
s.find( "even" )
und:
s.find( " don't" )
die unbestimmt sequenziert sind in Bezug auf:
s.replace(0, 4, "" )
Die beiden find
Aufrufe könnten vor oder nach dem ausgewertet werden replace
, was wichtig ist, da es eine Nebenwirkung hat s
, die das Ergebnis von find
verändert und die Länge von ändert s
. Je nachdem, wann dies in replace
Bezug auf die beiden find
Aufrufe ausgewertet wird, unterscheidet sich das Ergebnis.
Wenn wir uns den Verkettungsausdruck ansehen und die Bewertungsreihenfolge einiger Unterausdrücke untersuchen:
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^ ^ ^ ^ ^ ^ ^ ^
A B | | | C | | |
1 2 3 4 5 6
und:
.replace( s.find( " don't" ), 6, "" );
^ ^ ^ ^
D | | |
7 8 9
Beachten Sie, dass wir die Tatsache ignorieren, dass 4
und 7
weiter in weitere Unterausdrücke unterteilt werden können. So:
A
wird sequenziert, bevor B
sequenziert wird, bevor vorher C
sequenziert wirdD
1
zu 9
werden indeterminately in Bezug auf andere Unterausdrücke mit einigen der Ausnahmen unter sequenziert aufgelistet
1
zu 3
werden sequenziert vorB
4
zu 6
werden sequenziert vorC
7
zu 9
werden sequenziert vorD
Der Schlüssel zu diesem Problem ist:
4
in 9
Bezug auf unbestimmt sequenziert werdenB
Die mögliche Reihenfolge der Auswahl der Bewertung für 4
und 7
in Bezug auf B
erklärt den Unterschied in den Ergebnissen zwischen clang
und gcc
bei der Bewertung f2()
. In meinen Tests clang
auswertet , B
bevor die Bewertung 4
und 7
während gcc
auswertet nach. Wir können das folgende Testprogramm verwenden, um zu demonstrieren, was jeweils passiert:
#include <iostream>
#include <string>
std::string::size_type my_find( std::string s, const char *cs )
{
std::string::size_type pos = s.find( cs ) ;
std::cout << "position " << cs << " found in complete expression: "
<< pos << std::endl ;
return pos ;
}
int main()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
std::string copy_s = s ;
std::cout << "position of even before s.replace(0, 4, \"\" ): "
<< s.find( "even" ) << std::endl ;
std::cout << "position of don't before s.replace(0, 4, \"\" ): "
<< s.find( " don't" ) << std::endl << std::endl;
copy_s.replace(0, 4, "" ) ;
std::cout << "position of even after s.replace(0, 4, \"\" ): "
<< copy_s.find( "even" ) << std::endl ;
std::cout << "position of don't after s.replace(0, 4, \"\" ): "
<< copy_s.find( " don't" ) << std::endl << std::endl;
s.replace(0, 4, "" ).replace( my_find( s, "even" ) , 4, "only" )
.replace( my_find( s, " don't" ), 6, "" );
std::cout << "Result: " << s << std::endl ;
}
Ergebnis für gcc
( live sehen )
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
Ergebnis für clang
( live sehen ):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position even found in complete expression: 22
position don't found in complete expression: 33
Result: I have heard it works only if you believe in it
Ergebnis für Visual Studio
( live sehen ):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
Details aus dem Standard
Wir wissen , dass die Bewertungen der Unterausdrücke sofern nicht anders angegeben sind unsequenced, diese aus dem ist Entwurf C ++ 11 - Standard Abschnitt 1.9
Programmausführung , die sagt:
Sofern nicht anders angegeben, werden Auswertungen von Operanden einzelner Operatoren und von Unterausdrücken einzelner Ausdrücke nicht sequenziert. [...]
und wir wissen, dass ein Funktionsaufruf eine vor der Beziehung der Funktionsaufrufe sequenzierte Postfix-Expression und Argumente in Bezug auf den Funktionskörper einführt, aus Abschnitt 1.9
:
[...] Beim Aufrufen einer Funktion (unabhängig davon, ob die Funktion inline ist oder nicht) wird jede Wertberechnung und Nebenwirkung, die einem Argumentausdruck oder dem Postfix-Ausdruck zugeordnet ist, der die aufgerufene Funktion bezeichnet, vor der Ausführung jedes Ausdrucks oder jeder Anweisung sequenziert im Körper der aufgerufenen Funktion. [...]
Wir wissen auch, dass der Zugriff auf Klassenmitglieder und damit die Verkettung von links nach rechts ausgewertet wird, aus dem Abschnitt 5.2.5
Zugriff auf Klassenmitglieder, in dem Folgendes steht:
[...] Der Postfix-Ausdruck vor dem Punkt oder Pfeil wird ausgewertet. 64
Das Ergebnis dieser Auswertung bestimmt zusammen mit dem ID-Ausdruck das Ergebnis des gesamten Postfix-Ausdrucks.
Beachten Sie, dass in dem Fall, in dem der ID-Ausdruck eine nicht statische Elementfunktion ist, die Reihenfolge der Auswertung der Ausdrucksliste innerhalb von nicht angegeben wird, ()
da dies ein separater Unterausdruck ist. Die relevante Grammatik aus 5.2
Postfix-Ausdrücken :
postfix-expression:
postfix-expression ( expression-listopt) // function call
postfix-expression . templateopt id-expression // Class member access, ends
// up as a postfix-expression
C ++ 17 Änderungen
Der Vorschlag p0145r3: Verfeinerung der Ausdrucksauswertungsreihenfolge für idiomatisches C ++ hat mehrere Änderungen vorgenommen. Einschließlich Änderungen, die dem Code ein genau festgelegtes Verhalten verleihen, indem die Reihenfolge der Bewertungsregeln für Postfix-Ausdrücke und deren Ausdrucksliste verstärkt wird .
[Ausdruck] p5 sagt:
Der Postfix-Ausdruck wird vor jedem Ausdruck in der Ausdrucksliste und vor jedem Standardargument sequenziert . Die Initialisierung eines Parameters, einschließlich aller zugehörigen Wertberechnungen und Nebenwirkungen, wird in Bezug auf die eines anderen Parameters unbestimmt sequenziert. [Hinweis: Alle Nebenwirkungen von Argumentauswertungen werden vor Eingabe der Funktion sequenziert (siehe 4.6). —Ende Anmerkung] [Beispiel:
void f() {
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"); // OK
}
- Beispiel beenden]
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );