코드가 그것으로 인해 모든 부작용 함수 내에서 수행되기 때문에, 정의되지 않은 동작 호출하지 않는다하더라도 서브 표현식 평가 불특정 순서에없는 동작을 나타내는 선후 관계 도입 이 경우의 부작용과이.
이 예제는 N4228 : Refining Expression Evaluation Order for Idiomatic C ++ 제안서에 언급되어 있습니다. 이는 질문의 코드에 대해 다음과 같이 말합니다.
[...]이 코드는 전 세계의 C ++ 전문가들에 의해 검토되고 게시되었습니다 (The C ++ Programming Language, 4th edition). 그러나 지정되지 않은 평가 순서에 대한 취약성은 최근에야 도구 [.. .]
세부
함수에 대한 인수가 지정되지 않은 평가 순서를 가지고 있다는 것은 많은 사람들에게 명백 할 수 있지만이 동작이 연결된 함수 호출과 어떻게 상호 작용하는지는 분명하지 않을 것입니다. 이 사건을 처음 분석했을 때는 분명하지 않았고 모든 전문가 리뷰어 에게도 분명하지 않았습니다 .
언뜻보기에는 각각 replace
을 왼쪽에서 오른쪽으로 평가해야하므로 해당 함수 인수 그룹도 왼쪽에서 오른쪽으로 그룹으로 평가되어야하는 것처럼 보일 수 있습니다.
이것은 올바르지 않습니다. 함수 인수는 평가 순서가 지정되지 않았습니다. 연결 함수 호출은 각 함수 호출에 대해 왼쪽에서 오른쪽으로 평가 순서를 도입하지만, 각 함수 호출의 인수는 부분 인 멤버 함수 호출과 관련하여 이전에만 순서가 지정됩니다. 의. 특히 이는 다음 호출에 영향을줍니다.
s.find( "even" )
과:
s.find( " don't" )
다음과 관련하여 불확실하게 순서가 지정됩니다.
s.replace(0, 4, "" )
두 find
호출은 의 결과를 변경하는 방식으로 replace
부작용이 있기 때문에 중요 하며 길이를 변경합니다 . 따라서 두 호출과 관련하여 평가 되는시기에 따라 결과가 달라집니다.s
find
s
replace
find
연결 표현식을 살펴보고 일부 하위 표현식의 평가 순서를 살펴보면 :
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^ ^ ^ ^ ^ ^ ^ ^
A B | | | C | | |
1 2 3 4 5 6
과:
.replace( s.find( " don't" ), 6, "" );
^ ^ ^ ^
D | | |
7 8 9
4
그리고 7
더 많은 하위 표현식으로 더 세분화 될 수 있다는 사실을 무시 하고 있습니다. 그래서:
A
이전 B
에 시퀀싱되고 이전 C
에 시퀀싱됩니다.D
1
to 9
는 아래 나열된 일부 예외와 함께 다른 하위 표현식과 관련하여 불확실하게 시퀀스됩니다.
1
하기 3
전에 순서가있다B
4
하기 6
전에 순서가있다C
7
하기 9
전에 순서가있다D
이 문제의 핵심은 다음과 같습니다.
에 대한 평가 선택의 잠재적 인 순서 4
와 7
관련하여이 B
사이의 결과의 차이에 대해 설명 clang
하고 gcc
평가를 f2()
. 내 테스트에서는 clang
평가 B
하기 전에 평가 4
하고 평가하는 7
동안 gcc
평가합니다. 다음 테스트 프로그램을 사용하여 각 경우에 무슨 일이 일어나고 있는지 보여줄 수 있습니다.
#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 ;
}
에 대한 결과 gcc
( 실시간보기 )
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
결과 clang
( 실시간보기 ) :
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
결과 Visual Studio
( 실시간보기 ) :
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
표준의 세부 사항
우리는 지정하지 않는 한 하위 표현식의 평가가 순서가 지정되지 않았 음을 알고 있습니다. 이것은 C ++ 11 표준 섹션 1.9
프로그램 실행 초안 에서 가져온 것입니다.
언급 된 경우를 제외하고 개별 연산자의 피연산자 및 개별 식의 하위 식에 대한 평가는 순서가 지정되지 않습니다. [...]
그리고 우리는 함수 호출이 섹션에서 함수 본문과 관련하여 함수 호출 후위 표현식 및 인수의 관계 이전에 시퀀스를 도입한다는 것을 알고 있습니다 1.9
.
[...] 함수를 호출 할 때 (함수가 인라인인지 여부에 관계없이) 인수 표현식 또는 호출 된 함수를 지정하는 접미사 표현식과 관련된 모든 값 계산 및 부작용은 모든 표현식 또는 명령문을 실행하기 전에 순서가 지정됩니다. 호출 된 함수의 본문에서. [...]
또한 클래스 멤버 액세스 및 따라서 체인이 다음과 같은 5.2.5
클래스 멤버 액세스 섹션에서 왼쪽에서 오른쪽으로 평가된다는 것을 알고 있습니다 .
[...] 점 또는 화살표 앞의 접미사식이 평가됩니다. 64
그 평가의 결과는 id-expression과 함께 전체 접미사 식의 결과를 결정합니다.
id-expression 이 비 정적 멤버 함수로 끝나는 경우에는 별도의 하위 표현식이므로 내에서 expression-list 의 평가 순서를 지정하지 않습니다 ()
. 5.2
Postfix 표현식 의 관련 문법 :
postfix-expression:
postfix-expression ( expression-listopt) // function call
postfix-expression . templateopt id-expression // Class member access, ends
// up as a postfix-expression
C ++ 17 변경
제안 p0145r3 : 관용적 C ++에 대한 표현식 평가 순서 수정은 몇 가지 변경 사항을 적용했습니다. postfix-expressions 및 해당 expression-list에 대한 평가 규칙의 순서를 강화하여 코드에 잘 지정된 동작을 제공하는 변경 사항을 포함 합니다 .
[expr.call] p5 내용 :
postfix-expression은 expression-list 및 기본 인수의 각 표현식 앞에 순서가 지정됩니다 . 모든 관련 값 계산 및 부작용을 포함하는 매개 변수의 초기화는 다른 매개 변수와 관련하여 불확실하게 순서가 지정됩니다. [참고 : 인수 평가의 모든 부작용은 함수가 입력되기 전에 순서가 지정됩니다 (4.6 참조). —end note] [예 :
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
}
-예제 종료]
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );