이 코드 스 니펫에서 cout이“2 + 3 = 15”를 인쇄하는 이유는 무엇입니까?


126

왜 아래 프로그램의 출력이 무엇입니까?

#include <iostream>
using namespace std;

int main(){

    cout << "2+3 = " <<
    cout << 2 + 3 << endl;
}

생산

2+3 = 15

예상 대신

2+3 = 5

이 질문은 이미 여러 개의 닫기 / 다시 열기 주기로 진행되었습니다.

마감 투표에 앞서이 문제 에 대한 메타 토론을 고려 하십시오 .


96
;첫 번째 출력 줄 끝에 세미콜론 이 필요합니다 <<. 인쇄중인 것으로 생각하는 것을 인쇄하지 않습니다. cout << cout인쇄 하고 있습니다 1(사용 cout.operator bool()한다고 생각합니다). 그런 다음 5(에서 2+3)가 바로 다음에 나오는 숫자 15처럼 보이게합니다.
Igor Tandetnik

5
@StephanLechner 아마도 gcc4를 사용하고있을 것입니다. gcc5까지는 완전히 호환되는 스트림이 없었으며, 특히 그때까지 암시 적 변환이있었습니다.
Baum mit Augen

4
답이 시작되는 것처럼 들리는 @IgorTandetnik. 이 질문에는 처음 읽었을 때 분명하지 않은 미묘한 부분이 많이 있습니다.
Mark Ransom

14
사람들이 왜이 질문을 닫기 위해 투표를 계속합니까? "이 코드의 문제점을 알려주십시오."가 아니라 "이 코드가 왜이 출력을 생성합니까?" 첫 번째에 대한 대답은 "오타를 만들었습니다"입니다. 그러나 두 번째는 컴파일러가 코드를 해석하는 방법, 코드가 컴파일러 오류가 아닌 이유 및 포인터 주소 대신 "1"을 얻는 방법에 대한 설명이 필요합니다.
jaggedSpire

6
@jaggedSpire 인쇄상의 오류가 아니라면 의도적 인 것을 지적하지 않고 인쇄상의 오류처럼 보이는 특이한 구조를 의도적으로 사용하기 때문에 매우 나쁜 질문입니다. 어느 쪽이든, 가까운 투표를받을 자격이 있습니다. (. 인쇄 상 오류 또는 불량에 하나 때문에 AS / 악성이 도움을 찾는 사람들을위한 사이트는 사람들이 다른 사람을 속이려하지 않습니다.)
데이비드 슈워츠

답변:


229

의도적이든 우발적이든 <<, 첫 번째 출력 라인의 끝에 있습니다 ;. 그래서 당신은 본질적으로

cout << "2+3 = ";  // this, of course, prints "2+3 = "
cout << cout;      // this prints "1"
cout << 2 + 3;     // this prints "5"
cout << endl;      // this finishes the line

따라서 질문은 다음과 같이 요약됩니다. 왜 cout << cout;인쇄 "1"합니까?

이것은 아마도 놀랍게도 미묘한 것으로 판명되었습니다. std::cout는 기본 클래스를 통해 부울 컨텍스트에서 사용되는 특정 유형 변환 연산자std::basic_ios제공합니다 .

while (cout) { PrintSomething(cout); }

이것은 출력을 실패시키는 것이 어렵 기 때문에 꽤 좋지 않은 예입니다. 그러나 std::basic_ios실제로 입력 및 출력 스트림 모두의 기본 클래스이며 입력의 경우 훨씬 더 의미가 있습니다.

int value;
while (cin >> value) { DoSomethingWith(value); }

(스트림 끝에서 또는 스트림 문자가 유효한 정수를 형성하지 않을 때 루프에서 빠져 나옵니다).

이제이 변환 연산자의 정확한 정의가 표준의 C ++ 03과 C ++ 11 버전간에 변경되었습니다. 이전 버전에서는 operator void*() const;(일반적으로로 구현 됨 return fail() ? NULL : this;) 최신 버전에서는 explicit operator bool() const;(일반적으로 간단히 구현 됨 return !fail();)입니다. 두 선언 모두 부울 컨텍스트에서 제대로 작동하지만 이러한 컨텍스트 외부에서 사용되는 경우 다르게 동작합니다.

특히 C ++ 03 규칙에서는 다음 cout << cout과 같이 해석됩니다.cout << cout.operator void*() 일부 주소 되고 인쇄됩니다. C ++ 11 규칙에서는 cout << cout연산자가 선언 explicit되어 암시 적 변환에 참여할 수 없으므로 전혀 컴파일하지 않아야합니다 . 그것은 실제로 변화에 대한 주된 동기였으며, 무의미한 코드가 컴파일되는 것을 막았습니다. 어느 표준이든 준수하는 컴파일러는 인쇄하는 프로그램을 생성하지 않습니다 "1".

분명히 특정 C ++ 구현에서는 컴파일러와 라이브러리를 부적합한 결과를 생성하는 방식으로 혼합하고 일치시킬 수 있습니다 (@StephanLechner 인용 : "xcode에서 1을 생성하는 설정과 주소를 생성하는 다른 설정을 찾았습니다) : Language dialect "표준 라이브러리 libc ++ (c ++ 11을 지원하는 LLVM 표준 라이브러리)"와 결합 된 c ++ 98은 1을 생성하는 반면 libstdc (gnu c ++ 표준 라이브러리)와 결합 된 c ++ 98은 주소를 생성합니다. "). explicit변환을로 정의하는 C ++ 11 스타일 라이브러리와 결합 된 변환 연산자 (C ++ 11의 새로운 기능)를 이해하지 못하는 C ++ 03 스타일 컴파일러를 사용할 수 있습니다 operator bool(). 이러한 혼합을 통해 다음과 cout << cout같이 해석되어 cout << cout.operator bool()간단하게 cout << true인쇄 할 수있게 "1"됩니다.


1
@ TC이 특정 영역에서 C ++ 03과 C ++ 98 사이에는 차이가 없다고 확신합니다. 문제를 명확히하는 데 도움이된다면 C ++ 03의 모든 언급을 "pre-C ++ 11"로 바꿀 수 있다고 생각합니다. 나는 리눅스 등에서 컴파일러와 라이브러리 버전 관리의 복잡한 점에 전혀 익숙하지 않다. 저는 Windows / MSVC 사람입니다.
Igor Tandetnik

4
C ++ 03과 C ++ 98 사이에서 nitpick하려고하지 않았습니다. 요점은 libc ++가 C ++ 11 이상이라는 것입니다. C ++ 98 / 03을 따르지 않습니다.
TC

45

Igor가 말했듯이 C ++ 11 라이브러리 std::basic_ios를 사용하면이 operator bool대신을 가지고 operator void*있지만 어떻게 든 선언되지 않습니다 (또는로 취급) explicit. 여기를 참조 하십시오올바른 선언 를 .

예를 들어, 적합한 C ++ 11 컴파일러는 다음과 같은 결과를 제공합니다.

#include <iostream>
using namespace std;

int main() {
    cout << "2+3 = " << 
    static_cast<bool>(cout) << 2 + 3 << endl;
}

그러나 귀하의 경우 static_cast<bool>에는 묵시적 변환으로 (잘못) 허용됩니다.


편집 : 이것은 일반적이지 않거나 예상되는 동작이 아니므로 플랫폼, 컴파일러 버전 등을 아는 것이 유용 할 수 있습니다.


편집 2 : 참고로 코드는 일반적으로 다음과 같이 작성됩니다.

    cout << "2+3 = "
         << 2 + 3 << endl;

또는

    cout << "2+3 = ";
    cout << 2 + 3 << endl;

버그를 노출시키는 두 가지 스타일을 혼합하고 있습니다.


1
첫 번째 제안 된 솔루션 코드에 오타가 있습니다. 운영자가 너무 많습니다.
eerorika

3
지금도하고 있습니다. 전염성이 있어야합니다. 감사!
쓸모없는

1
하아! :) 내 대답의 초기 편집에서 세미콜론 추가를 제안했지만 줄 끝에서 연산자를 알지 못했습니다. 나는 OP와 함께 이것이 가능한 오타의 가장 중요한 순열을 생성했다고 생각합니다.
eerorika

21

예기치 않은 출력의 이유는 오타입니다. 당신은 아마 의미

cout << "2+3 = "
     << 2 + 3 << endl;

출력이 예상되는 문자열을 무시하면 다음과 같이 남습니다.

cout << cout;

C ++ 11부터는 형식이 잘못되었습니다. std::cout는 암시 적으로 std::basic_ostream<char>::operator<<(또는 멤버가 아닌 오버로드) 받아 들일 수있는 것으로 변환 할 수 없습니다 . 따라서 표준 준수 컴파일러는 적어도이 작업을 수행하도록 경고해야합니다. 컴파일러가 프로그램 컴파일을 거부했습니다.

std::cout 로 변환 가능 bool 하고 스트림 입력 연산자의 부울 오버로드는 1의 관찰 출력을 갖습니다. 그러나 해당 오버로드는 명시 적이므로 암시 적 변환을 허용해서는 안됩니다. 컴파일러 / 표준 라이브러리 구현이 표준을 엄격하게 준수하지 않는 것 같습니다.

C ++ 11 이전의 표준에서는 잘 구성되어 있습니다. 당시에 std::coutvoid*스트림 입력 연산자 과부하 가있는 암시 적 변환 연산자가있었습니다 . 그러나 그 결과는 다릅니다. std::cout객체 의 메모리 주소를 인쇄 합니다.


11

게시 된 코드는 C ++ 11 (또는 이후 호환 컴파일러)에 대해 컴파일해서는 안되지만 C ++ 11 이전 구현에 대한 경고조차없이 컴파일해야합니다.

차이점은 C ++ 11이 스트림을 부울로 명시 적으로 변환했다는 것입니다.

C.2.15 조항 27 : 입 / 출력 라이브러리 [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4

변경 : 기존 부울 변환 연산자에서 명시 적 사용을 지정합니다.
이론적 : 의도를 명확하게하고, 해결 방법을 피하십시오.
원래 기능에 미치는 영향 : 암시 적 부울 변환에 의존하는 유효한 C ++ 2003 코드는이 국제 표준으로 컴파일되지 않습니다. 이러한 변환은 다음 조건에서 발생합니다.

  • bool 유형의 인수를 취하는 함수에 값을 전달하는 것;
    ...

ostream 연산자 <<는 bool 매개 변수로 정의됩니다. bool 로의 변환이 존재하고 (명시 적이 지 않음) pre-C ++ 11이므로 1 cout << cout로 변환됩니다 cout << true.

그리고 C.2.15에 따르면, 이것은 C ++ 11로 시작하여 더 이상 컴파일되지 않아야합니다.


3
boolC ++ 03에는 변환이 없었지만 std::basic_ios::operator void*()조건부 또는 루프의 제어 식으로 의미가 있습니다.
벤 Voigt

7

이런 식으로 코드를 쉽게 디버깅 할 수 있습니다. cout출력 을 사용할 때 버퍼링되므로 다음과 같이 분석 할 수 있습니다.

첫 번째 발생이 cout버퍼 를 나타내고 연산자가 버퍼 <<의 끝에 추가하는 것을 나타냅니다. <<귀하의 경우 연산자의 결과 는 출력 스트림 cout입니다. 당신은 다음에서 시작합니다 :

cout << "2+3 = " << cout << 2 + 3 << endl;

위에서 언급 한 규칙을 적용하면 다음과 같은 일련의 작업이 수행됩니다.

buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

전에 말했듯이 결과 buffer.append()는 버퍼입니다. 시작시 버퍼가 비어 있고 처리 할 다음 명령문이 있습니다.

성명서: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);

완충기: empty

먼저 buffer.append("2+3 = ")주어진 문자열을 버퍼에 직접 넣고가됩니다 buffer. 이제 당신의 상태는 다음과 같습니다 :

성명서: buffer.append(cout).append(2 + 3).append(endl);

완충기: 2+3 = 

그 후, 당신은 당신의 진술을 계속 분석하고 당신 cout은 버퍼의 끝에 덧붙이 기 위해 논쟁의 여지가 있습니다. 이 cout로 처리됩니다 1당신이 추가 할 수 있도록1 하여 버퍼의 끝. 이제 당신은이 상태에 있습니다 :

성명서: buffer.append(2 + 3).append(endl);

완충기: 2+3 = 1

다음으로 버퍼에있는 것은 2 + 3출력 연산자보다 더하기가 우선 순위가 높으므로 먼저이 두 숫자를 추가 한 다음 결과를 버퍼에 넣습니다. 그 후에 당신은 얻는다 :

성명서: buffer.append(endl);

완충기: 2+3 = 15

마지막으로 endl버퍼 끝에 값을 추가 하면 다음이 있습니다.

성명서:

완충기: 2+3 = 15\n

이 프로세스 후에 버퍼의 문자가 버퍼에서 표준 출력으로 하나씩 인쇄됩니다. 따라서 코드의 결과는 2+3 = 15입니다. 당신이 이것을 보면 당신은 인쇄하려고 시도 1에서 추가를 얻을 cout. << cout당신의 진술에서 제거 하면 원하는 결과를 얻을 수 있습니다.


6
이것은 모두 사실이지만 (아름답게 형식화되어 있지만) 질문을 던지고 있다고 생각합니다. 나는 그 질문이 "왜 처음부터 cout << cout생산 1하는가?" 그리고 삽입 연산자 체인에 대한 토론 도중에 있다고 주장했습니다.
쓸모없는

1
아름다운 형식화를 위해 +1. 이것이 첫 번째 답변이라고 생각하면 도움을
주신 것이 좋습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.