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


98

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

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'입니다.


30
설명을 요청하셨습니까? 나는 종종 잠재적 인 후보자를 인터뷰하고 질문을받는 데 관심이 많고 흥미를 보입니다.
Brady

3
@jrok 정의되지 않은 동작입니다. 구현이 수행하는 모든 작업 (상사에게 귀하의 이름으로 모욕적 인 이메일을 보내는 것을 포함)은 준수합니다.
James Kanze

2
이 질문은 시퀀스 포인트를 언급하지 않는 C ++ 11 ( 현재 버전의 C ++) 답변을 요구합니다. 불행히도 저는 C ++ 11의 시퀀스 포인트 교체에 대해 충분히 알지 못합니다.
CB Bailey

3
그것은 수 없습니다 확실히 그것을 정의되지되지 않은 경우 10, 그 중 하나가 될 것이다 0100. ( c++항상 값으로 평가하게 c했다 전에 증가되고). 그리고 그것이 정의되지 않았더라도 여전히 끔찍하게 혼란 스러울 것입니다.
leftaroundabout

2
아시다시피, "cout << c ++ << c"라는 제목을 읽을 때 잠시 C와 C ++ 언어와 "cout"이라는 다른 언어 간의 관계에 대한 설명으로 생각했습니다. 당신은 알고 누군가가 그 "cout을이"C ++은 C보다 열등 훨씬이었다 C ++, 그 열등 정도라고 생각하는 방법을 말하는 것처럼 - "cout을이"매우 것을 아마도 이행 성으로 매우 많은 열등한 : C.에
tchrist

답변:


145

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

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 ++에 대한 표현식 평가 순서 구체화 를 참조하십시오.


@Maxim : 설명해 주셔서 감사합니다. 설명 한 호출로 정의되지 않은 동작이 될 것입니다. 하지만 이제 한 가지 더 질문이 있습니다 (실러 질문 일 수 있으며 기본적이고 크게 생각하는 것이 누락 됨). ostream :: operator <대신 std :: operator << ()의 글로벌 버전이 호출된다는 것을 어떻게 추론 했습니까? <() 멤버 버전. 디버깅 할 때 글로벌 버전이 아닌 ostream :: operator << () 호출의 멤버 버전을 사용하고 있으며 이것이 처음에 대답이 01
pravs

@Maxim 다르게 만드는 것은 아니지만 ctype int이 있으므로 operator<<여기에 멤버 함수가 있습니다.
James Kanze

2
@pravs : operator<<멤버 함수인지 독립 함수 인지 여부 는 시퀀스 포인트에 영향을주지 않습니다.
Maxim Egorushkin

11
'시퀀스 포인트'는 더 이상 C ++ 표준에서 사용되지 않습니다. 부정확하고 'sequenced before / sequenced after'관계로 대체되었습니다.
Rafał Dowgird

2
So the result of the above is undefined.귀하의 설명은 정의되지 않은 경우 가 아니라 지정되지 않은 경우 에만 유용합니다 . JamesKanze는 그의 대답에서 그것이 어떻게 정의되지 않는지 설명했습니다 .
Deduplicator

68

기술적으로 전반적으로 이것은 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] 입니다.


1
기술적으로, 동작은 정의되지 않은데, 개체가 수정되고 중간 시퀀스 지점없이 다른 곳에서 액세스하기 때문입니다. 정의되지 않음은 지정되지 않습니다 . 구현에 더 많은 여유가 있습니다.
James Kanze

1
@Als 예. 나는 당신의 편집을 보지 못했습니다 (프로그램이 이상한 일을 할 수 없다는 jrok의 진술에 반응했지만). 당신의 편집 된 버전은 그것이가는 한 좋지만, 제 생각에 핵심 단어는 부분적인 순서입니다 . 시퀀스 포인트는 부분적인 순서 만 도입합니다.
James Kanze

1
@Als 정교한 설명에 감사드립니다.
pravs

4
새로운 C ++ 0x 표준은 본질적으로 동일하지만 섹션과 문구가 다릅니다. :) 인용문 : (1.9 Program Execution [intro.execution], par 15) : "스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 또 다른 부작용 또는 동일한 스칼라 객체의 값을 사용하는 값 ​​계산, 동작은 정의되지 않았습니다. "
Rafał Dowgird

2
이 답변에 버그가 있다고 생각합니다. "std :: cout << c ++ << c;" std :: operator << (std :: ostream &, int)가 없기 때문에 "std :: operator << (std :: operator << (std :: cout, c ++), c)"로 번역 할 수 없습니다. 대신 "std :: cout.operator << (c ++). operator (c);"로 변환되며, 실제로 "c ++"와 "c"의 평가 사이에 시퀀스 포인트가 있습니다 (오버로드 된 연산자는 따라서 함수 호출이 반환 될 때 시퀀스 포인트가 있습니다.) 결과적으로 동작 및 실행 순서 지정됩니다.
크리스토퍼 스미스

20

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

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) 을 결정하는 것 외에 액세스 하면 동작이 정의되지 않습니다.


표준에 대한 인용문 중 하나입니다. "설명 된 경우 제외"인 IIRC에는 오버로드 된 연산자를 처리 할 때 예외가 포함되어 연산자를 함수로 처리하므로 std :: ostream :: operator << (int)에 대한 첫 번째 호출과 두 번째 호출 사이에 시퀀스 포인트를 생성합니다. ). 내가 틀렸다면 나를 바로 잡으십시오.
크리스토퍼 스미스

@ChristopherSmith 오버로드 된 연산자는 함수 호출처럼 동작합니다. 만약이 c정의 된 사용자와 사용자 유형이었다 ++대신, int결과가 지정되지 않은 것,하지만 정의되지 않은 동작이 없을 것이다.
James Kanze 2013 년

1
당신이 둘 사이에 순서 지점 보는가 @ChristopherSmith c의를 foo(foo(bar(c)), c)? 함수가 호출되고 반환 될 때 시퀀스 포인트가 있지만 두 평가 사이에는 함수 호출이 필요하지 않습니다 c.
James Kanze 2014 년

1
@ChristopherSmith cUDT라면 오버로드 된 연산자 함수 호출이고 시퀀스 포인트를 도입하므로 동작이 정의되지 않습니다. 그러나 하위 표현식 c이 이전 또는 이후에 평가 되었는지 여부는 여전히 지정되지 c++않으므로 증분 버전을 얻었는지 여부를 지정하지 않을 것입니다 (이론상 매번 같을 필요는 없습니다).
James Kanze 2014

1
@ChristopherSmith 시퀀스 포인트 이전의 모든 것이 시퀀스 포인트 이후의 모든 것보다 먼저 발생합니다. 그러나 시퀀스 포인트는 부분적인 순서 만 정의합니다. 예를 들어 해당 표현식에서 하위 표현식 c과 사이에 시퀀스 포인트가 없으므로 c++두 가지가 임의의 순서로 발생할 수 있습니다. 세미콜론에 관해서는 ... 그것들은 완전한 표현 인 한 시퀀스 포인트만을 유발합니다. 다른 중요한 시퀀스 포인트는 함수 호출입니다. f(c++)는 증가 cf, 쉼표 연산자, &&||보고 ?:시퀀스 포인트를 유발합니다.
James Kanze 2015 년

4

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


4
이 질문은 잘못된 프로그래밍 관행을 나타낼 수 있습니다 (그리고 심지어 유효하지 않은 C ++). 그러나 대답은 무엇이 잘못되었고 왜 잘못되었는지를 나타내는 질문 에 대답 해야합니다 . 질문에 대한 해설은 완벽하게 타당하더라도 답이 아닙니다. 기껏해야 답변이 아닌 의견 일 수 있습니다.
PP
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.