64비트 정수 출력할 때 주의할 점

컴퓨터 CPU 단위에서는 짧은 시간에 아주 엄청난 규모의 일들이 일어난다. 2.5Ghz CPU라면 1초에 클럭이 25억번 온다는 소리고 아주 이상적인 경우, 최신 CPU들은 1초에 25억개에 근접한, 아니면 그 이상의 명령어들을 처리할 수 있다. 그래서 고작 42억개까지만 기억할 수 있는 32비트 정수로는 CPU 내부의 통계치를 담기에는 너무 부족하다. 그래서 대부분 주요 CPU 내부 상태, 예를 들어 클럭 발생 횟수나 지금까지 수행한 명령어 개수는 64비트로 표현을 해야한다. 64비트는 대략:

18,446,744,073,709,551,616

1천 8백 경에 해당하는 숫자이다. 총 19자리 숫자. 우주의 나이 대략 100억년 광년을 킬로미터 단위 (대략 9.4조 Km/LY)로 바꾸면 10^22가 나오니 이런 엄청난 숫자에 대략 버금간다.

프로파일링 툴을 하나 만들고 있다. 이 툴은 프로그램이 수행한 명령어 개수 따위를 헤아린다. 이렇게 컴퓨터 내부에서 일어나는 숫자의 단위가 엄청남을 잘 알지만 결과에 나오는 수천억이 훌쩍 넘는 명령어가 처리 되었다는 결과를 보면 아찔하긴 하다. 첨엔 “왜 이렇게 많어?” 라고 의심을 하고 내가 프로파일 툴을 잘못 짰나 의심이 갈 정도다. 그것도 고작 몇 분 돌아가는 프로그램이 저런 수천억, 경단위의 명령어들을 처리한다.

 

그런데 이런 통계 출력 과정 중에 대단한 삽질을 하나 하였는데, 지난 번에 한 번 printf와 cout이 만들어주는 구문을 비교하며 cout의 비효율성(?)을 지적한 바 있다. 그러나 수행성능을 고려치 않으면 확실히 cout이 더욱 편할 때가 있는데 32비트 및 64비트 정수형이 혼재되어있거나 플랫폼 독립적으로 만들고 싶을 때는 삽질을 꽤나 줄일 수 있다. 플랫폼에 상관없이, 타입 신경쓰지 않고 그냥 << 만 주면 되니 편리하다.

unsigned __int64 aa = 79012597414007;
int bb = 12345;
float cc = 75.1f;
printf("%u, %d, %f\n", aa, bb, cc);

바보처럼 실수로 64비트 정수를 그냥 32비트 정수 찍듯이 %u를 주고 말았다. 그런데 이 사태로 벌어지는 일은..

1915930505, 18396, 2681561585995870000000000000... (생략)

대략 멍하다. 이런 망가진 결과를 보고 난 한참이나 어디 자료구조가 꼬이거나 메모리 버그가 났나보다 한참을 들여다 봤다. 그러나 순전히 printf에서 실수를 한 결과. %u는 4바이트만 읽는데 인자로는 8바이트를 줬고 이것이 밀려서 괴상하게 찍힌 것이다. 위 예와 같이 특히 실수형이 있으면 숫자가 아름답게 나오기 때매 메모리 관련 버그로 의심하기 십상이다.

이럴 땐 VC++ (msvcrt) 에선 %I64d, GNU libc에서는 %lld와 같이 줘야한다. Unsigned일 때는 d 대신에 u가 되어야 한다. x86-64 환경이라해도 int는 4바이트이므로 알아서 잘 찍어주겠지라고 생각하단 역시 삽질한다. 플랫폼마다, 비트 크기에 따라, signed/unsigned에 따라 %?를 맞추는 것이 여간 삽질이 아니다. 이런 점에선 확실히 C++ stream을 이용하는 것이 좋다. 또 여기에 유니코드 문자열을 출력할 때 %s냐 %S냐의 삽질도 만만치 않다.

by object | 2008/06/08 14:59 | 컴퓨터 | 트랙백 | 핑백(1) | 덧글(11)
트랙백 주소 : http://minjang.egloos.com/tb/1925078
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at art.oriented : p.. at 2008/06/15 19:31

... tf 생성 코드 비교: http://minjang.egloos.com/1888099 64비트 정수 출력시 주의할 점: http://minjang.egloos.com/1925078 심심해서 직접 printf와 cout의 수행속도를 비교해봤다. 매우 정확하게 하려면 CPU 시뮬레이터에다 넣어 ... more

Commented by nVec at 2008/06/08 16:32
확실히 알아서 처리해주는게 편리한 것 같긴해요
하지만 알아서 처리해주기만 하면 학습할 기회가 적어지는 것 같고요.
Commented by sayhappy at 2008/06/08 22:12
printf 잘못걸리면 정말 삽질이죠 -_-;
저는 값이 문제라고 생각하고 디버깅했었죠.;;쩝

Commented by 방준영 at 2008/06/09 00:19
printf("%" PRId64 "\n", i);처럼 PRI* 매크로를 활용하면 이식성에 도움이 됩니다. 물론 점점 누더기가 되어 가는 C 코드는 피할 수 없지만요. :-)
Commented by object at 2008/06/09 09:54
아 하하.. ㅠㅠ
Commented by rein at 2008/06/09 07:45
Bjarne Stroustrup은 '언젠가는' cout이 printf보다 빨라질 것이다라고 -- 컴파일 타임에 타입을 알게 되니 -- 했다고 하지요. 하지만 cout 에서 출력되는 코드를 실제로 뜯어보면(후략);

근데 cout 이 타입을 알아서 해준다는게 너무 편해서(...), 요즘 사용하는 패킷 생성코드는 cout 과 거의 동일한 방식으로 타입처리를 하게 해봤습니다 ~_~
Commented by object at 2008/06/09 09:55
흠 또 그렇긴 하네요. 컴파일 때 타입을 알아버리니.. 시간내서 정확하게 성능 비교를 해봐야겠군요. 퍼포먼스 크리티컬 하지만 않다면 확실히 버그를 줄일 수 있는 cout 스타일이 좋겠죠.

그런데 Bjarne의 그런 발언은 별로 동의할 수는... 같은 맥락으로 10년전부터 줄기차게 Java가 언젠가는 C를 따라잡는다고 그러죠. 물론 HotJava 등의 일부는 우수한 성능을 보이기도 하지만..
Commented by 최재훈 at 2008/06/09 10:48
항상 MSDN을 펼쳐놓고 확인해야지요. printf는 몇번이나 실수한 적이 있어서...

p.s. 블로그 디자인이 바뀌었군요.
Commented by K at 2008/06/09 13:41
갑자기 궁금해서 실례를 무릅쓰고 여기다 질문 올립니다. ^^;; Printf도 컴파일 시간에 타입을 알 수 있지 않나요? 'cout이 printf보다 빨라질 것이다라고 -- 컴파일 타임에 타입을 알게 되니 --' --> 잘이해가 안되네요.... 전에 글을 보면 cout의 경우 클래스 생성 등에서의 코드양이 많아져서 느린걸로 알고 있었는데 그게 아닌가요?
Commented by object at 2008/06/09 13:59
컴파일러가 부가기능으로 컴파일할 때, %d같은 것과 뒤의 실제 인자를 비교해서 워닝 정도는 띄어줄 수는 있습니다. 그러나 특정 타입에 따라 어떻게 출력하게 될 것인가는 printf가 실제로 돌아갈 때 결정이 되죠. 그래서 printf 코드를 보면 큰 스위치 문에서 각 타입별로 분기문이 있습니다. 인자를 스택에서 꺼내고 %?를 보면서 타입을 결정하는 것이 실제 수행시간에 일어나죠.

반면 cout은 <<가 수 많은 타입에 대해 오버로딩이 되어있고 컴파일 시간에 타입을 알아내어 특정 타입의 함수가 바로 불려지는 장점이 있습니다. 이 점을 Bjarne이 언급한 것 같네요.

일단 언뜻보기에 cout 코드가 뱉어내는 코드 양이 엄청 많습니다. <<가 수십개 중첩되면 정말 엄청나죠. 그러나 printf도 내부에서 그 만큼 루프와 분기문을 거쳐야 하기 때문에 dynamically executed instruction으로 따져보면 얼마나 날지 좀 궁금하네요. 시간 나거든 직접 얼마나 많은 명령어가 실행되는지 테스트 해봐야겠네요. PIN이라는 툴을 쓰면 쉽게 알아낼 수 있습니다.
Commented by 방준영 at 2008/06/09 14:54
조금 더 정확히 구분하자면 같은 libc라 할지라도 32비트 버전의 libc에서는 "%lld"를 쓰지만 64비트 버전의 libc에서는 그냥 "%ld"로 씁니다(ㅌ.ㅌ). 그래서 C99에서는 PRId64같은 매크로를 써서 이식성을 꾀한 것이죠.
Commented by object at 2008/06/09 15:14
그렇네요. 더 암울하네요.

그런데 일단 제가 x86-64 레드햇에서 돌리는데 거기서 %lld로 해도 일단 나오기는 잘 나오네요;; long int, long long int 모두 8바이트로 나옵니다. 명시적으로 width 64로 표기하는 것이 훨씬 쉽네요.

:         :

:

비공개 덧글

<< 이전 페이지 다음 페이지 >>





by 김민장 2008 이글루스 TOP 100
최근 등록된 덧글
개발자 입장에서의 수많은 ..
by Jiyoon at 02/04
저도 아들 돌잔치때 돌잡이 ..
by 박상욱 at 01/18
미국 대학원 원서 작성중에 p..
by 태클사이야 at 01/13
TO: 박PD 로그인 하지 않아..
by 박응용 at 01/10
http://gigglehd.com/zbx..
by dhunter at 12/28
우와.. 좋네요. 태반이 ..
by 윤광배 at 12/17
항상 좋은 글 잘 보고 있습니..
by y2k at 11/23
글이 좋아서 제 블로그에 담..
by 쏭섭 at 11/23
최근 등록된 트랙백
조엘 스폴스키의 강연 (Sta..
by 인덕원칸타타
[Redis] sds.c를 분..
by 조급하지말고 천천히
메뉴릿
이글루 파인더

website counter

Add to Google

rss

skin by 이글루스