C/C++ 식 평가 - printf("%d %d %d\n", a, a++, a);
트위터에서 재미있는 글을 보았습니다. ^^

#include <stdio.h>
int main()
{
int a = 5;
printf("%d %d %d\n", a, a ++, a);
return 0;
}
저도 예상한 답이 틀렸습니다. ^^ printf가 __cdecl 호출 규약을 따르기 때문에 함수의 인자는 우측부터 전달하게 되어 식 평가가 가장 마지막 인자인 a가 먼저 전달되고, 두번째로 a ++가 평가되어 6으로 증가한 값이 첫 번째 a로 할당되어 "6 5 5"가 나올 거라 예상한 것입니다.
왜 "6 5 6" 이 나왔는지는 어셈블리 코드를 통해 유추할 수 있습니다.
printf("%d %d %d\n", a, a++, a);
010417D5 8B 45 F8 mov eax,dword ptr [a]
010417D8 89 85 30 FF FF FF mov dword ptr [ebp-0D0h],eax // [ebp-0D0h]의 값에 초기 값 5를 보관
010417DE 8B 4D F8 mov ecx,dword ptr [a]
010417E1 83 C1 01 add ecx,1 // ecx == 6
010417E4 89 4D F8 mov dword ptr [a],ecx
010417E7 8B 55 F8 mov edx,dword ptr [a]
010417EA 52 push edx // ecx의 값이 전달되었으므로 6
010417EB 8B 85 30 FF FF FF mov eax,dword ptr [ebp-0D0h]
010417F1 50 push eax // [ebp-0d0h]의 값이 전달되었으므로 5
010417F2 8B 4D F8 mov ecx,dword ptr [a]
010417F5 51 push ecx // ecx의 값이 전달되었으므로 6
010417F6 68 30 6B 04 01 push offset string "%d %d %d\n" (01046B30h)
010417FB E8 20 FB FF FF call _printf (01041320h)
재미있군요. 그래도 전 저렇게 코드를 생산한 것이 Visual C++의 독자적인 식 평가라고 생각했습니다. 하지만, 온라인 컴파일러를 통해 GCC에서 테스트 해보니,
Code, Compile & Run
; https://www.codechef.com/ide
역시 같은 결과가 나왔습니다. Visual C++과 GCC 컴파일러가 동일한 결과를 낸 걸로 봐서 어떤 규칙이 있을 듯한데, 일단 결과만 놓고 억지로 해석해 보면 이럴 것 같습니다.
1. printf로 전달되는 인자의 수식이 평가됩니다.
2. 즉, 첫 번째 a는 평가할 것이 없으므로 코드 출력이 안되고,
3. a++는 a 값을 보존한 다음에 1을 증가시키고
4. 마지막 a도 평가할 것이 없으므로 코드 출력이 안됨.
5. 인자에 대한 식 평가는 끝났고 이제 값을 스택으로 전달하는데,
6. 마지막 a는 현재 변수 a에 보존된 값을 그대로 전달.
7. 중간의 a++ 인자는 수식이 평가된 것이므로 보존된 값을 전달.
8. 첫 번째 a도 현재 변수 a에 보존된 값을 그대로 전달.
뭐... 그냥 제 나름대로의 해석일 뿐입니다. 혹시 정확하게 아시는 분은 덧글 부탁드리겠습니다. ^^
참고로, Visual C++의 릴리스 모드로 컴파일 시에는 "5 5 5" 출력 결과를 낸다는 차이점이 있습니다.
int a = 5;
printf("%d %d %d\n", a, a++, a);
00D61000 6A 05 push 5
00D61002 6A 05 push 5
00D61004 6A 05 push 5
00D61006 68 08 21 D6 00 push offset string "%d %d %d\n" (0D62108h)
00D6100B E8 20 00 00 00 call printf (0D61030h)
00D61010 83 C4 10 add esp,10h
반면, GCC에서는 최적화 모드에서도 "6 5 6"으로 동일하게 나왔습니다.
C/C++ 스펙에 정확하게 명시하고 있지 않다면, 이건 어느 쪽의 버그다라고 규정짓기 힘듭니다. 따라서, 이런 식의 코드는 웬만하면 개발자가 명시적으로 지정하는 것이 좋을 것입니다. ^^
그러고 보니, 프로그래밍 언어는 결과를 예측하기 힘든 경우가 종종 있는 것 같습니다. 마치 SSE 지원 유무에 따라 결과가 바뀌었던 다음의 글이 그랬던 것처럼.
C# - 부동소수 계산 왜 이렇게 나오죠? (2)
; https://www.sysnet.pe.kr/2/0/10873
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]