[c++] “The C ++ Programming Language”제 4 판 섹션 36.3.6의이 코드가 잘 정의 된 동작을 가지고 있습니까?

Bjarne Stroustrup의 The C ++ Programming Language 4 판 섹션 36.3.6 STL-like Operations 에서 다음 코드는 연결 의 예로 사용됩니다 .

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" ) ;
}

어설 션은 gcc( live ) 및 Visual Studio( live ) 실패하지만 Clang을 사용할 때 실패하지 않습니다 ( live ).

다른 결과가 나오는 이유는 무엇입니까? 이러한 컴파일러 중 하나가 체인 식을 잘못 평가하고 있습니까? 아니면 이 코드가 어떤 형태의 지정되지 않거나 정의되지 않은 동작을 보여줍 니까?



답변

코드가 그것으로 인해 모든 부작용 함수 내에서 수행되기 때문에, 정의되지 않은 동작 호출하지 않는다하더라도 서브 표현식 평가 불특정 순서에없는 동작을 나타내는 선후 관계 도입 이 경우의 부작용과이.

이 예제는 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부작용이 있기 때문에 중요 하며 길이를 변경합니다 . 따라서 두 호출과 관련하여 평가 되는시기에 따라 결과가 달라집니다.sfindsreplacefind

연결 표현식을 살펴보고 일부 하위 표현식의 평가 순서를 살펴보면 :

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
  • 1to 9는 아래 나열된 일부 예외와 함께 다른 하위 표현식과 관련하여 불확실하게 시퀀스됩니다.
    • 1하기 3전에 순서가있다B
    • 4하기 6전에 순서가있다C
    • 7하기 9전에 순서가있다D

이 문제의 핵심은 다음과 같습니다.

  • 49대해 불확실하게 순서가 지정됩니다.B

에 대한 평가 선택의 잠재적 인 순서 47관련하여이 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
}

-예제 종료]


답변

이것은 C ++ 17과 관련된 문제에 대한 정보를 추가하기위한 것입니다. 위의 코드를 인용 한 문제 를 해결하기 위한 제안 ( Refining Expression Evaluation Order for Idiomatic C ++ Revision 2 ) C++17은 표본과 같습니다.

제안 된대로 제안 및 인용 (내 강조 표시)에서 관련 정보를 추가했습니다.

현재 표준에 지정되어있는 표현식 평가 순서는 조언, 인기있는 프로그래밍 관용구 또는 표준 라이브러리 시설의 상대적 안전성을 약화시킵니다. 함정은 초보자 나 부주의 한 프로그래머만을위한 것이 아닙니다. 그것들은 우리가 규칙을 알고 있더라도 우리 모두에게 무차별 적으로 영향을 미칩니다.

다음 프로그램 조각을 고려하십시오.

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");
}

어설 션은 프로그래머가 의도 한 결과를 검증하기위한 것입니다. 일반적인 표준 관행 인 멤버 함수 호출의 “체인”을 사용합니다. 이 코드는 전 세계의 C ++ 전문가에 의해 검토되고 게시되었습니다 (The C ++ Programming Language, 4th edition). 그러나 지정되지 않은 평가 순서에 대한 취약성 은 최근에야 도구에 의해 발견되었습니다.

이 논문은 30 년 넘게 C++17영향을 받아 C존재 해 온 표현 평가의 순서에 대한 기본 규칙을 변경할 것을 제안했다 . 언어가 현대 관용구를 보장 하거나 “모호하고 버그를 찾기 어려운 함정과 소스”를 위험에 노출 시켜야한다고 제안했습니다 . 위의 코드 표본에서 일어난 것과 같은 .

에 대한 제안 C++17모든 표현식에 잘 정의 된 평가 순서가 있어야한다는 것입니다 .

  • 후위 식은 왼쪽에서 오른쪽으로 평가됩니다. 여기에는 함수 호출 및 멤버 선택식이 포함됩니다.
  • 할당 식은 오른쪽에서 왼쪽으로 평가됩니다. 여기에는 복합 할당이 포함됩니다.
  • 이동 연산자에 대한 피연산자는 왼쪽에서 오른쪽으로 평가됩니다.
  • 오버로드 된 연산자를 포함하는 표현식의 평가 순서는 함수 호출 규칙이 아니라 해당 내장 연산자와 연관된 순서에 의해 결정됩니다.

위의 코드는 GCC 7.1.1Clang 4.0.0.


답변