cout << a ++ << a;에 대한 정답은 무엇입니까? 선택 b에

최근 인터뷰에서 다음과 같은 객관적인 질문이있었습니다.

int a = 0;
cout << a++ << a;

답변:

ㅏ. 10
b. 01
c. 정의되지 않은 동작

나는 선택 b에 대답했다. 즉 출력은 “01”이 될 것이다.

그러나 놀랍게도 면접관은 정답이 옵션 c : 정의되지 않음이라고 들었습니다.

이제 C ++의 시퀀스 포인트 개념을 알고 있습니다. 다음 문에 대해서는 동작이 정의되지 않았습니다.

int i = 0;
i += i++ + i++;

하지만 내가 문에 대한 이해에 따라 cout << a++ << a는이 ostream.operator<<()첫 번째와 두 번 호출됩니다 ostream.operator<<(a++)나중에 ostream.operator<<(a).

또한 VS2010 컴파일러에서 결과를 확인했으며 출력도 ’01’입니다.



답변

다음을 생각할 수 있습니다.

cout << a++ << a;

같이:

std::operator<<(std::operator<<(std::cout, a++), a);

C ++는 이전 평가의 모든 부작용이 시퀀스 지점 에서 수행되었음을 보장합니다 . 함수 인수 평가 사이에는 시퀀스 포인트가 없습니다. 이는 인수가 인수 a이전 std::operator<<(std::cout, a++)또는 이후에 평가 될 수 있음 을 의미합니다 . 따라서 위의 결과는 정의되지 않았습니다.


C ++ 17 업데이트

C ++ 17에서는 규칙이 업데이트되었습니다. 특히:

시프트 연산자 식 E1<<E2E1>>E2에서의 모든 값 계산 및 부작용 E1은의 모든 값 계산 및 부작용 이전에 시퀀싱됩니다 E2.

어느 것이 생산 결과의 코드를 필요로한다는 의미 b있는 출력을 01.

자세한 내용은 P0145R3 관용적 C ++에 대한 표현식 평가 순서 구체화 를 참조하십시오.


답변

기술적으로 전반적으로 이것은 Undefined Behavior 입니다.

그러나 대답에는 두 가지 중요한 측면이 있습니다.

코드 설명 :

std::cout << a++ << a;

다음과 같이 평가됩니다.

std::operator<<(std::operator<<(std::cout, a++), a);

표준은 함수에 대한 인수 평가 순서를 정의하지 않습니다.
따라서 다음 중 하나 :

  • std::operator<<(std::cout, a++) 먼저 평가되거나
  • a먼저 평가되거나
  • 구현 정의 순서 일 수 있습니다.

이 순서는 표준에 따라 미지정 [Ref 1] 입니다.

[참고 1] C ++ 03 5.2.2 함수 호출
8 항

인수 평가 순서는 지정되지 않습니다 . 인수 표현식 평가의 모든 부작용은 함수가 입력되기 전에 적용됩니다. 접미사 식 및 인수 식 목록의 평가 순서는 지정되지 않습니다.

또한 함수에 대한 인수 평가 사이에는 시퀀스 포인트가 없지만 모든 인수를 평가 한 후에 만 ​​시퀀스 포인트가 존재합니다 [Ref 2] .

[참고 2] C ++ 03 1.9 프로그램 실행 [intro.execution] :
17 항 :

함수를 호출 할 때 (함수가 인라인인지 여부에 관계없이) 모든 함수 인수 (있는 경우)를 평가 한 후 함수 본문의 표현식 또는 명령문을 실행하기 전에 발생하는 시퀀스 지점이 있습니다.

여기에서의 값은 c중간 시퀀스 지점없이 두 번 이상 액세스되고 있으며 이에 대해 표준은 다음과 같이 말합니다.

[참고 3] C ++ 03 5 표현식 [expr] :
4 항 :

….
이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에서 스칼라 객체는 표현식 평가에 의해 최대 한 번 수정 된 저장된 값을 가져야합니다. 또한 이전 값은 저장할 값을 결정하기 위해서만 액세스해야합니다 . 이 단락의 요구 사항은 전체 표현의 하위 표현의 허용 가능한 각 순서에 대해 충족되어야합니다. 그렇지 않으면 동작이 정의되지 않습니다 .

코드는 c시퀀스 포인트를 간섭하지 않고 두 번 이상 수정 하며 저장된 객체의 값을 결정하기 위해 액세스하지 않습니다. 이는 위의 조항에 대한 명백한 위반이므로 표준에서 요구하는 결과는 Undefined Behavior [Ref 3] 입니다.


답변

시퀀스 포인트는 부분 순서 만 정의합니다 . 귀하의 경우 (과부하 해결이 완료되면) :

std::cout.operator<<( a++ ).operator<<( a );

a++대한 첫 번째 호출과의
std::ostream::operator<<사이에 시퀀스 포인트가 a있고에 대한 두 번째 호출과 두 번째 호출 std::ostream::operator<<사이에 시퀀스 포인트가 있지만 a++과 사이에는 시퀀스 포인트가 없습니다 a. 유일한 순서 제약은에 a++대한 첫 번째 호출 전에 완전히 평가 (부작용 포함) operator<<하고 두 번째 a는에 대한 두 번째 호출 전에 완전히 평가된다는 것 operator<<입니다. (인과적인 순서 제약도 있습니다. 두 번째 호출 operator<<은 첫 번째 결과가 인수로 필요하기 때문에 첫 번째 호출 보다 우선 할 수 없습니다.) §5 / 4 (C ++ 03)은 다음과 같이 설명합니다.

언급 된 경우를 제외하고 개별 연산자의 피연산자 및 개별 식의 하위 식에 대한 평가 순서와 부작용이 발생하는 순서는 지정되지 않습니다. 이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에서 스칼라 객체는 표현식 평가에 의해 최대 한 번 수정 된 저장된 값을 가져야합니다. 또한 이전 값은 저장할 값을 결정하기 위해서만 액세스해야합니다. 이 단락의 요구 사항은 전체 표현의 하위 표현의 허용 가능한 각 순서에 대해 충족되어야합니다. 그렇지 않으면 동작이 정의되지 않습니다.

식의 허용되는 순서 중 하나는 a++,, a에 대한 첫 번째 호출 operator<<,에 대한 두 번째 호출입니다 operator<<. 이것은 a( a++) 의 저장된 값을 수정하고 새 값 (두 번째 a) 을 결정하는 것 외에 액세스 하면 동작이 정의되지 않습니다.


답변

정답은 질문하는 것입니다. 독자가 명확한 답을 볼 수 없기 때문에 그 진술은 받아 들일 수 없습니다. 그것을 보는 또 다른 방법은 우리가 문장을 해석하기 훨씬 더 어렵게 만드는 부작용 (c ++)을 도입했다는 것입니다. 간결한 코드는 의미가 명확하면 훌륭합니다.


답변