c++ 간단한 질문입니다.

hwanikani의 이미지

#include
using namespace std;

int getNum()
{
cout << "Getting Number" << endl;
return 77;
}

int main()
{
cout << "1 " << getNum() << " 2" << endl;
}

위 코드 실행시

Getting Number
1 77 2

가 나오는데 Getting Number가 먼저 나오는 이유가 뭐죠?

제 예상으로는

1 Getting Number
77 2

이게 나올줄 알았는데 ㅠ
왼쪽에서 오른쪽으로 실행이 되기 전에 getNum()이 먼저 한번 훑어지더라구요.

요약: cout << "1 " << getNum() << " 2" << endl;) 에서 getNum()이 먼저 한번 훑어(?)지는 것 같은 현상이 일어나는 이유가 뭔가요?

ifree의 이미지

getNum() 가 먼저 evaluate되면서 side effect 가 발생하기 때문이죠.

hwanikani의 이미지

cout << "1 " << getNum() 만 function식으로 바꿔보면

operator<<(cout, "1 ").operator<<(getNum())인데

여기서 getNum()이 먼저 evaluate 되는 이유가 궁금해서 그러네요.

정상적이라면 왼쪽에서 오른쪽으로 evaluate 한다고 생각했는데

여기선 getNum()을 먼저 evaluate 한다음에

전체 식을 왼쪽에서 오른쪽으로 다시 진행하더라구요.

뭔가 그 룰 같은게 있나요?

cinsk의 이미지

함수 인자가 평가되는 순서는 알 수 없기 때문입니다. 컴파일러 맘입니다.

물론, 그 함수가 시작되기 전에 모든 인자가 평가되어야 하지만, 단지 그것 뿐입니다. 언제, 어떤 순서로 평가될지 전혀 알 수 없습니다.

ifree의 이미지

cout << "1 " << getNum() 를

<<( <<( cout, "1 " ), getNum() ) 로 생각할 수 있고,

오퍼레이터 << 의 리턴값은 cout 입니다.

두번째 인수인 getNum() 는 evaluate 되면서 side effect 로 문자열이 출력되고.
첫번째 인수인 <<( cout, "1 " ) 는 evaluate 되면서 cout 가 리턴되고 문자열을 버퍼에 저장하지만 바로 출력되지 않고 statement 가 끝날 때 문자열을 출력하고 버퍼를 flush 하게 됩니다.

hwanikani의 이미지

식을 처리할때 함수 안에 argument들을 evaluate할때 오른쪽에서 왼쪽으로 처리해서 그런 결과가 나오는것 같네요.
(스택에 argument들이 오른쪽에서 왼쪽순으로 올라가서 그런가 봅니다).

따라서 <<( <<( cout, "1 " ), getNum() ) 식에서도 getNum()이 먼저 evaluate된다음에
<<(cout, "1 ")이 evaluate 되는거 같구요 (디버그 모드로 보니깐 getNum()을 무조건 먼저 evaluate 하더군요).

아래는 제가 테스트할때 쓴 코드입니다.

#include
using namespace std;

int number()
{
cout << "number() called" << endl;
return 44;
}

int numA(int a)
{
cout << "numA() called" << endl;
return 77;
}

int numB(int b)
{
cout << "numB() called" << endl;
return 77;
}

int numC(int c)
{
cout << "numC() called" << endl;
return 77;
}

void testFunc(int a, int b, int c)
{
cout << "testFunc() called" << endl;
}

int main()
{
testFunc(numA(number()),numB(3),numC(3));
}

실행시 numC(3)이 먼저 evaluate되고 그다음 numB(3), 그리고 numA(number())가 evaluate되더군요.

(결과 출력시:
numC() called
numB() called
number() called
numA() called
testFunc() called)

cinsk의 이미지

Stack에 인자가 들어가는 순서(흔히 calling convention이라고 하는 것들)와, 인자가 평가되는 순서는 별개입니다.

Calling convention은 대개, 컴파일러 및 CPU에 따라 정해져 있으며, 보통 해당 architecture ABI 문서에 나와 있습니다.

이에 비해 인자가 평가되는 순서는 알 수 없습니다. (unspecified).

unipro의 이미지

이런 코드는 피하는게 좋다고 들었습니다.
이와 비슷한 예로,
func_a(func_b(), func_c());
와 같은 것이 있습니다.
10년 전 책에서, func_b, func_c 중에 어떤 것이 먼저 호출이 될지 구현마다 다를 수 있다고 하더라구요.

이식성을 제외하고서라도, 코드를 읽을 때 명확하게 순서가 들어오지 않으면 좋은 코드가 아니잖아요.

내 블로그: http://unipro.tistory.com

hwanikani의 이미지

제가 이 궁금증이 생긴 이유가 책에서 Exception에 대해서 읽고 있는데 아래와 같은 코드가 나오더군요 (간략하게 요약).

#include
using namespace std;

double divide(double x, double y) throw (const char*)
{
if (y == 0)
throw "Can't divide by 0";
return x / y;
}

int main()
{
double x, y;
cin >> x >> y;

try
{
cout << x << " divided by " << y << " is " << divide(x,y) << endl;
}
catch (const char*)
{
cout << "Can't divide by 0" << endl;
}
}

근데 여기서 y값으로 0을 입력하면
cout << x << " divided by " << y << " is " << divide(x,y) << endl; 에서
"x divided by y is "는 출력을 안하고 바로 divide(x,y)에서 exception을 잡아 내더라구요.
(divide(x,y)를 먼저 evaluate한다는 뜻)

그것때문에 궁금증이 생기더라구요.

ifree의 이미지

인수의 evaluation 순서와 무관하게,
cout << ... << ...; 에서
문자열이 출력되는 시점의 문제입니다.

hwanikani의 이미지

아까

cout << "1 " << getNum() 를

<<( <<( cout, "1 " ), getNum() ) 로 생각할 수 있고,

오퍼레이터 << 의 리턴값은 cout 입니다.

두번째 인수인 getNum() 는 evaluate 되면서 side effect 로 문자열이 출력되고.
첫번째 인수인 <<( cout, "1 " ) 는 evaluate 되면서 cout 가 리턴되고 문자열을 버퍼에 저장하지만 바로 출력되지 않고 statement 가 끝날 때 문자열을 출력하고 버퍼를 flush 하게 됩니다.

라고 적어주셨는데

첫번째 인수인 <<(cout, "1 ")이 evaluate된다면 거기서 문자열이 출력 되야 하지 않나요?

ifree의 이미지

cout << "a1" << "a2" << "a3" ; 에서
a1, a2, a3 가 차례대로 출력되는 것이 아니고, 각각이 버퍼에 저장된 후 마지막에 일괄적으로 출력되기 때문입니다.
다음 코드를 실행해 보면 알 수 있을 듯.

string F()
{
    string temp;
    cin >> temp;
    cout << "your input is " + temp << endl;
    return temp;
}
 
int main()
{
    cout << "You have input " << F() + ", " << F() + ", " << F() << endl;
    return 0;
}

hwanikani의 이미지

버퍼 문제는 아닌것 같네요...

string F()
{
string temp;
cin >> temp;
cout << "your input is " + temp << endl;
return temp;
}

int main()
{
cout << "You have input " << flush << F() + ", " << F() + ", " << F() << endl;
return 0;
}

flush를 시켜봐도 똑같은것 같네요.

ifree의 이미지

flush 는 output 을 하고 버퍼를 비웁니다.
따라서 위 경우는 flush 가 있으나 없으나 차이가 없습니다.
문자열이 화면에 출력되는 시점은 cout 를 포함하는 statement 가 끝나는 순간인데,
개행 문자나 flush 를 이용해 미리 출력할 수는 있습니다.

hwanikani의 이미지

죄송하지만 문자열이 화면에 출력되는 시점이(버퍼가 되는 시점) cout을 포함하는 statement가 끝나는 순간이라고 어디 나와 있나요?
(싸우려는 말투가 아니라 정말 궁금해서요 ㅠㅠ)

slee님 답글 보다가 갑자기 buffer관련 궁금증이 생겨서 열심히 뒤져 보고 있는데 (관련 질문도 올림;; http://kldp.org/node/119536)
책이든 msdn이든 열심히 뒤져봐도
1. endl
2. flush
3. cin 입력 대기중
4. 버퍼 사이즈 꽉 참.

이 네가지 컨디션이 아니면 flush가 자동으로 된다는 법이 없더라구요.

혹시나 cerr처럼 unitbuf 플래그가 set되어 있는줄 알았는데 (http://www.cplusplus.com/reference/iostream/manipulators/unitbuf/)
cin하고 cout은 unitbuf 플래그가 set되어 있는것도 아니더라구요...

이상하게 자동으로 flush가 되는 이유가 뭐죠?

ifree의 이미지

아 일단, 저 위에 개행문자가 아니고, endl.

버퍼를 출력하는 시점에 대한 자료를 찾을 수가 없네요.
appropriate time 에 버퍼를 출력한다고 하는데 좀 더 검색해 봐야 할 듯.

jick의 이미지

위에서 다들 잘 써주셨지만...

cout << "1 " << getNum() << " 2" << endl;

이렇게 쓰면 헷갈리니까 <<를 왼쪽부터 함수 f1, f2, f3, f4로 바꾸면,

f4(f3(f2(f1(cout, 1), getNum()), 2), endl)

이렇게 되지 않습니까?

여기서 확실히 보장이 되는 선후관계는 다음뿐입니다. (일단 인자를 다 계산해야 그 인자를 쓰는 함수를 부르겠죠.)
f1->f2->f3->f4
getNum->f2->f3->f4

보시다시피 getNum은 f2를 부르기 전에만 부르면 될 뿐 f1을 실행하기 전에 먼저 불러도 아무런 이상이 없습니다.

DarkSide의 이미지

...

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.