본문 바로가기

카테고리 없음

디버그 assert trace


이번 시간에는 가변 매크로 함수를 이용하는 ASSERT() 함수에 대해서 알아봅니다.

추가 하고 싶은 디버깅 관련 함수들이 정말 많은데... 본강좌도 진행해야 하므로...

일단은 오늘 살펴볼 ASSERT() 함수까지만 만들어보기로 하고요.

다음에 기회가 되면 좀 더 재미난 디버깅 메소드 들을 살펴보기로 하겠습니다. 약속~


오늘 살펴 볼 ASSERT() 함수, 정확히는 C++에서 제공하는 assert() 함수가 그 원형인데요.

대부분 assert()만 사용하기에는 뭔가 부족한점이 많기 때문에, 대부분의 프로그래머들은

자기 입맛에 맞게 MACRO 함수나 일반 함수로 ASSERT와 같이 래핑해서 사용하는 경우가 많습니다.

그렇다면 과연 ASSERT() 함수는 어떠한 용도로 사용되는 함수인가...

assert() 함수의 기본 원형은 다음과 같습니다.

void assert( int nExpression )

언제나 그랬듯이... 실제 원형은 이렇게 간단하지 만은 않습니다.

실제로 assert() 함수를 호출하면, 내부적으로는 _assert()나 _wassert() 함수를 호출하게 되는데...

뭐 거기서 뭘하는지는 소스 코드를 보면 대충은 알수 있겠지만, 거기까지는 우리가 알필요가 없겠죠.

어쨋든, 위와 같이 int형 수식을 인자로 받아서 그 식이 참이면 아무런 작업도 하지 않고 리턴하지만,

그 식이 FALSE, 즉 0이라면 아래와 같은 에러 메시지를 출력하면서 프로그램을 종료시킵니다.

사용자 삽입 이미지

이렇게 어느 파일의 몇번째 라인에서 에러가 났는지까지도 친절하게 알려주는 디버깅 함수입니다.

ASSERT()는 실제 상용프로그램에서는 정말 수백 수천개까지도 사용하는 디버깅 함수인데...

실제로 아주 간단한 프로그램을 만들더라도, 이 ASSERT 함수는 아주 남용 수준으로 써주는 것이 좋습니다.

실제로 엄청난 대형 프로그램, 그래, 우리가 디아블로3를 개발하는 프로그래머라고 생각을 해봅시다!

이렇게 거대한 프로그램을 개발하는 경우, 이 변수에 1이라는 값이 들어가야하는데 2가 들어갔다면,

바로 다음줄에서 에러가 나는 경우는 많지 않습니다. 수십, 수백 수천줄 뒤에서나 에러가 나게 되고.

수백, 수천줄 뒤라는 의미는 함수를 이미 몇십, 몇백개를 거쳐 지나온 후에 에러가 나버린다는 의미가 됩니다.

과연 그 상황에서 여러분은 어디서 에러가 났는지 상상이나 할수 있으시겠습니까?

그룹 프로그래밍 등을 할때 동료 프로그래머에게 내 소스를 넘겨줬는데 그 동료 프로그래머가

"아, 정말! 니 소스는 정말 ASSERT 창이 너무 많이 떠서 짜증나서 못봐주겠다!" 라는 소리가 나올정도로

남용해서 써야 합니다. "누가 잘못된 값을 넣으래? 1을 넣어야 하는 곳에는 1을 넣어야지!"하고 대답해주면 떙이죠.

1을 넣어야 하는 곳에 2를 넣었을때의 결과는 수십, 수백개의 함수를 거쳐서 프로세스를 거치는 동안

숫자 하나로 인해 발생한 문제가 아주 심각한 오류를 만들어서 버그 투성이의 프로그램을 만들수도 있습니다.

사전에 그런 사태를 막기 위해서 절대로 이곳에는 이 값만이 들어와야 한다 하는 곳에는 무조건 ASSERT()!


자, 그렇다면 ASSERT()를 어떤 식으로 구현했는지... 한번 살펴보도록 하겠습니다.

이 ASSERT()는 역시 제가 자주 사용하는 방법입니다. ASSERT()가 거기서 거기라고 생각할수 있지만,

복잡하게 만들자면, 정말 엄청나게 복잡한 매크로 함수로 변신하기도 하는 함수가 바로 ASSERT()입니다.


01: #pragma once
02: 
03: #ifdef __DEBUG
04: 
05: #include <stdio.h>
06: #include <conio.h>
07: // assert() 함수를 위한 헤더.
08: #include <assert.h>
09: 
10: // 디버그 창의 폭(Width) 지정.
11: #define LOG_WINDOW_WIDTH    400
12: 
13:     //
14:     // 디버깅을 위한 유틸리티 함수들.
15:     //
16: 
17:     // 로그 시스템 초기화
18:     void INIT_LOG(void);
19:     // 로그 시스템 해제.
20:     void FREE_LOG(void);
21: 
22:     // 디버깅용 로그 출력 함수.
23:     void TRACE(LPSTR szDbgFormat, ...);
24:     // 디버깅용 로그 출력 함수 with Show Flag.
25:     void TRACE(BOOL bShowLog, LPSTR szDbgFormat, ...);
26: 
27:  // 디버그 시에 쓰이는 ASSERT() 함수.
28:  #define ASSERT(_EXP_)   TRACE("ASSERT() : %s\n\t%s(%d)", \
29:                                __FILE__, __FUNCTION__, __LINE__); \
30:                          assert( (_EXP_) );
31:  #define ASSERTEX(_EXP_, _EXPINFO_, ...) \
32:                   TRACE("ASSERT() : %s\n\t%s(%d)\n\t" _EXPINFO_, \
33:                         __FILE__, __FUNCTION__, __LINE__, \ 
##__VA_ARGS__); \
34: assert( (_EXP_) ); 35: 36: #else 37: // 디버그 모드가 아니면 아무것도 실행하지 않음. 38: #define INIT_LOG __noop 39: #define FREE_LOG __noop 40: #define TRACE __noop 41: #define ASSERT __noop 42: #endif

역시 DBLog.h 파일에 추가 했습니다.

그런데 이번에는 일반 함수가 아닌 매크로 함수로 선언 했는데요.

ASSERT() 함수는 반드시 매크로 함수로 구현해야 합니다. 때에 따라서는 일반 함수로 구현하기도 하지만,

일반적인 경우는 가급적 거의 무조건 매크로 함수로 구현 하는 것이 좋습니다.

왜냐하면, ASSERT 함수를 일반 함수로 호출하게 되면, 호출 시점이 Assert() 함수 내부가 되지만,

매크로 함수로 ASSERT() 함수를 구현하게 되면, 호출 시점이 해당 소스 그 위치가 되므로,

로그를 남기기에도 용이하고, 디버깅 시에도 호출 스택을 하나 더 증가 시킬일이 없어집니다.


이제 실제로 ASSERT() 함수를 살펴보도록 합시다~

뭔가 되게 복잡해보이는데요. 사실은 별거 없습니다. assert() 함수를 호출해주기 이전에

단지 TRACE() 함수를 이용해서 로그에도 ASSERT() 함수가 사용되었음을 기록해 주는것 뿐입니다.

다만 여기서 사용된 특이한 키워드가 눈에 뜨입니다. __FILE__, __FUNCTION__, __LINE__이 바로 그것인데요.

__FILE__은 말 그대로 해당 매크로 함수를 호출한 파일의 파일명을 char* 타입의 문자열로 제공하고,

__FUNCTION__은 역시 해당 매크로 함수가 호출된 부모 함수의 이름을 리턴합니다.

__LINE__은 당연히 해당 매크로 함수가 호출된 라인 번호일 테구요.


두번째로 눈여겨 봐야할 부분은 바로 가변 매크로 함수의 사용입니다.

ASSERT() 함수의 확장판이라고 할수 있는 ASEERTEX() 함수인데요.

ASSERT() 함수와 동일하지만, 로그에 출력할 메시지를 좀더 추가할수 있습니다.

ASSERT() 함수가 호출되면, 프로그램이 그냥 종료 되버리기 때문에, 추가적인 정보를 제공하기가 쉽지 않습니다.

ASSERT() 함수에서 제공하는 파일명과 라인수만 보고 찾아갈 수도 있겠지만,

왜 거기서 ASSERT() 함수가 호출되었는지, 소스를 보기전까지는 정확히 알수가 없죠.

따라서, 그룹 프로그래밍 등을 할때에 ASSERT() 함수에다가 추가적으로 전달할 정보를 더 기록할 수 있습니다.

가변 매크로 함수에 대해서는 특별히 따로 설명하지 않겠습니다. 궁금하신 점이 있으시다면 댓글 남겨주시구요.

다음 번에 기회가 된다면 가변 매크로 함수에 대해서도 알아보기로 하고 오늘은 여기까지..^^;


휴... 이것으로 예상했던 것보다 꽤 길게 써버린 디버깅 강좌도 끝이 났습니다.

앞으로도 디버깅 관련 강좌는 계속 올려드릴 것이지만, 어쨋든 1부 끝~~

다음 시간부터는 드디어 다시 본강좌에 돌입하도록 하겠습니다.

오늘 만든 내용도 첨부파일로 올려드리니 역시 다운 받아서 참고 하시길 바랍니다.

그럼 다음 시간에 뵐께요~~