출처 :
http://www.gilgil.co.kr/bbs/view.php?id=lecture&page=1&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=headnum&desc=asc&no=641
-------------------- cpp code --------------------
#include "iostream.h"
void main()
{
char *gilgil = "gilgil";
char *babo = " babo\r\n";
char *gilgil_babo = "gilgil babo\r\n"
cout << gilgil_babo; // (1)
cout << gilgil; cout << babo; // (2)
cout << gilgil << babo; // (3)
return;
}
(1), (2), (3) 전부 동일한 출력을 하고 있으나, 구동 방식은 다음과 같이 전부 다릅니다.
-------------------- assembly pseudo code --------------------
(1) cout << gilgil_babo;
push dword ptr "gilgil babo";
push dword ptr [std::cout]
call std::basic_ostream
add esp, 0x08
(2) cout << gilgil; cout << babo;
push dword ptr "gilgil"
push dword ptr [std::cout]
call std::basic_ostream
add esp, 0x08
push dword prt "babo"
push dword ptr [std::cout]
call std::basic_ostream
add esp, 0x08
(3) cout << gilgil << babo;
push dword ptr "babo"
push dword ptr "gilgil"
push dword ptr [std::cout]
call std::basic_ostream
add esp, 0x08 <--- "gilgil" 먼저 제거
push eax <--- [결론2] 증거
call std::basic_ostream
add esp, 0x08 <--- "babo" 나중에 제거
[결론1]
<< 연산자는 인자(parameter)를 2개 가집니다.
하나는 실제 인자(먼저 push)이고 다른 하나는 cout 포인터(나중에 push)이죠.
(엄격히 따지면 cout 포인터는 메소드 call에 있어서 this 로 봐야 합니다)
<< 연산자의 수행은 함수 입장에서 봤을 때에는
cdecl calling convention이며(파라미터를 오른쪽에서부터 거꾸로 넣는다)
연속하는 << 연산자의 수행의 우선순위는 left-to-right 입니다(왼쪽부터 먼저 처리한다).
이를 헷갈리면 안됩니다!!!
즉, 함수 call을 위해 스택을 이용한 파라미터를 넘기는 방식은 cdecl이고
<< 를 수행하는 순서는 "gilgil"(왼쪽)부터 먼저 처리하고 나중에 "babo"(오른쪽)를 처리한다는 뜻입니다.
(3)번을 보면 언뜻 "babo"를 먼저 처리하고 "gilgil"을 나중에 처리하는 것처럼 보일 수 있지만
call std::basic_ostream이 이루저 지고 나서 esp에 0x08값을 빼어 내는 것을 보면
"gilgil"이 먼저 처리되고 나중에 "babo"가 처리되는 것을 알 수 있습니다.
처음 call에서는 쓰이지도 않을 "babo"를 제일 먼저 push하는 것을 보면
이는 operand와 operator를 postfix 변환을 하면서 stack상에서 처리하는 방식과 유사합니다.
("gilgil"과 "babo"가 하나의 함수내에서 한꺼번에 처리되는 게 아니다).
[결론2]
연속하는 << 연산자를 사용하는 경우 std::basic_ostream를 call하기 이전에
[std::cout]의 값을 push하지 않고 eax를 push하는 것을 보면
std::basic_ostream의 result는 cout의 pointer로 eax 레지스터에 저장이 되고
이 값(eax)이 다시 활용되는 것을 볼 수 있으며
이는 Compiler 기본적인 최적화의 한 방법으로 보여 집니다.
재미있네요. ^^
C코드로 해석해 본 gilgil의 쉽게 풀어 쓰는코드 ~~~
-------------------- gilgil(?) pseudo code --------------------
extern ostream *cout;
ostream *basic_ostream(ostream *cout, char *data)
{
// 주절주절 알아서 일을 함...
return cout;
}
void main()
{
char *gilgil = "gilgil";
char *babo = " babo\r\n";
char *gilgil_babo = "gilgil babo\r\n";
basic_ostream(cout, gilgil_babo); // (1)
basic_ostream(cout, gilgil); basic_ostream(cout, babo) // (2)
basic_ostream(basic_ostream(cout, gilgil), babo); // (3)
return;
}
[테스트환경]
Borland C++Builder 5
[개인적인 결론]
C++에서 operator overloading은 코드를 쉽게 읽을 수 있도록 readability는 향상되지만
code optimization 부분에서 분석할 때에는 오히려 더 힘들다는 생각이 듭니다(헷갈려요).
그냥 평범한 메소드 call 이 속편하죠. ^^
연산자는 함수의 다른 형태일 뿐입니다.
사람이 인식하기에 연산자가 직관성이 좋기 때문에, 연산자 오버로딩을 쓰는 거죠.
그런데 왜 gilgil....?