메모리를 동적으로 할당(new) 한 후 해제(delete) 되지 않았을때 Visual C++ 에서 알아내는 방법에 대하여 알아보자.
먼저 디버깅 모드에서 동적 메모리를 추적할 수 있도록 아래와 같이 매크로를 정의한다.
Toggle line numbers 1 #ifdef _DEBUG
2 #define new new( _CLIENT_BLOCK, __FILE__, __LINE__ )
3 #endif
동적 메모리의 추적은 디버그 모드에서만 사용가능하다.
동적 메모리를 어떻게 추적할지를 제어하기 위하여 설정하는 것으로 메모리를 할당하기 전에 실행해야 한다.
동적 메모리의 추적 방법을 제어하는 비트 필드는 다음과 같다.
비트 필드 | 기본값 | 설명 |
_CRTDBG_ALLOC_MEM_DF | 설정 | 디버그 할당을 사용한다. 이 비트를 해제하면 할당은 함께 연결되어 있지만 블록 형식은 _IGNORE_BLOCK 이다. |
_CRTDBG_DELAY_FREE_MEM_DF | 해제 | 부족한 메모리 조건을 시뮬레이션하는 것과 관련해서 메모리가 실제로 해제되는 것을 방지한다. 이 비트를 설정하면, 해제된 블록이 디버그 힙의 연결 리스트에 보관되지만 _FREE_BLOCK으로 표시되고 특별한 바이트 값으로 채워진다. |
_CRTDBG_CHECK_ALWAYS_DF | 해제 | 모든 할당 및 할당 취소에서 _CrtCheckMemory를 호출하게 한다. 실행 속도는 지연되지만 오류를 신속하게 찾아낸다. |
_CRTDBG_CHECK_CRT_DF | 해제 | _CRT_BLOCK 형식으로 표시된 블록을 누수 탐지와 상태 구별 작업에 포함시킨다. 비트를 해제하면 위와 같은 작업을 하는 동안 런타임 라이브러리가 내부적으로 사용하는 메모리를 무시한다. |
_CRTDBG_LEAK_CHECK_DF | 해제 | _CrtDumpMemoryLeaks를 호출하여 프로그램을 종료할 때 누수 검사를 수행한다. 응용 프로그램이 할당한 모든 메모리를 해제하는 데 실패하면 오류 보고서가 생성된다. |
Toggle line numbers 1 #include <stdio.h>
2 #include <string.h>
3 #include <crtdbg.h>
4
5
6 #ifdef _DEBUG
7 #define new new( _CLIENT_BLOCK, __FILE__, __LINE__ )
8 #endif
9
10 int main()
11 {
12
13 _CrtSetDbgFlag( _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF );
14
15 char* s1;
16 char* s2;
17
18 s1 = new char[128];
19 strcpy( s1, "string1" );
20
21 s2 = new char[256];
22 strcpy( s2, "string2" );
23
24 delete[] s1;
25
26 return 0;
27 }
디버그 모드로 실행후 프로그램이 종료되면 아래의 Visual Studio 의 출력창에 다음과 같은 결과가 출력된다.
----------------------------------------------------------------------------
'debug.exe': 'C:\temp\debug\Debug\debug.exe' 로드, 기호가 로드되었습니다.
'debug.exe': 'D:\WINXP\system32\ntdll.dll' 로드, 기호가 로드되지 않았습니다.
'debug.exe': 'D:\WINXP\system32\kernel32.dll' 로드, 기호가 로드되지 않았습니다.
Detected memory leaks!
Dumping objects ->
c:\temp\debug\main.cpp(21) : {44} client block at 0x003710B0, subtype 0, 256 bytes long.
Data: <string2 > 73 74 72 69 6E 67 32 00 CD CD CD CD CD CD CD CD
Object dump complete.
'[3340] debug.exe: 기본' 프로그램이 0 (0x0) 코드에서 끝났습니다.
----------------------------------------------------------------------------
여기에서 아래의 메세지는 main.cpp 의 21 번째 줄에서 할당한 256 바이트가 해제 안된 것을 알려준다.
출처 : http://www.viper.pe.kr/cgi-bin/moin.cgi/Visual_C%2B%2B_%EC%97%90%EC%84%9C_%EB%8F%99%EC%A0%81_%EB%A9%94%EB%AA%A8%EB%A6%AC_%EC%B2%B4%ED%81%AC%ED%95%98%EA%B8%B0#bottom
그럼 이제 간단히 첨부된 파일 C_Memory.h 파일을 보면서 메모리 릭 발생여부를 알아내고 이를 해결하는 방법에 대해서 알아보자.
이는 MS 사에서 CRT(C Runtime) 라이브러리로 제공해는 CRTDBG 를 이용할 것이다.
일단 프로그램 정의부에 다음과 같이 추가한다.
#include <crtdbg.h> #define CRTDBG_MAP_ALLOC #ifdef _DEBUG #define new new( _NORMAL_BLOCK, __FILE__, __LINE__ ) //궁금하시면 제 게시글 중 <매크로 마법> 편을 보세요. #endif |
이제 메인 진입 부 처음에 다음과 같이 선언하고. 일반 main() 이라고 가정하자.
void main() { _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); // .... 메모리를 동적 할당하는 무언가의 작업을 하겠죠 } |
추가로 설명하자면, _CrtSetDbgFlag 설정 비트 필드 값은 다음과 같다.
_CRTDBG_ALLOC_MEM_DF : 디버그 힙 메모리 할당을 허용하고, 또한 _CLIENT_BLOCK과 같은 사용자 정의 메모리 블럭을 사용한다.
_CRTDBG_LEAK_CHECK_DF : 프로그램 종료 시 자동으로 메모리 릭을 찾고 이를 출력하고, 이것은 _CrtDumpmemoryLeaks 함수를 호출한 것과 같은 결과이다.
이제 프로그램을 디버그 모드로 실행하고 종료했을 때, 만약 동적 메모리 해제를 제대로 안해줬다면 다음과 같은 결과가 결과 화면에 출력될 것이다.
우리에게 필요한 것은 바로 빨간색 선을 그은 정보이다. 우리는 이 번호를 이용해 메모리 릭이 일어나는 부분을 찾아갈 수 있다. 그러나 중요한 점은 똑같은 환경에서 다시 실행했을 때도 메모리 릭 현상 결과가 같아야 한다는 것이다. 그렇지 않으면, 매번 실행할 때마다 메모리 릭이 발생한 위치가 달라지고 따라서 위 번호도 달라져 버리기 때문이다. 따라서, 다른 작업을 하지 마시고 디버깅 작업만 하길 바란다.
일단 위 9637 번호를 이용해서 메모리 릭이 일어나는 곳을 찾아가보자. 이제 다시 main 첫부분에 다음과 같이 추가한다.
void main() _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); _CrtSetBreakAlloc( 9637 ); // .... 메모리를 동적 할당하는 무언가의 작업을 한다... } |
그리고 다시 디버그 모드로 실행을 하면 9637에 지시하는 똑같은 메모리 번지를 알아서 브레이크 포인터를 걸어준다.
우리는 그 부분의 소스를 보고 콜 스택을 추적해 가면서 메모리 릭을 감지하고 해결할 수가 있다.
이런식으로 출력창에 출력되었던 다른 메모리 릭도 모두 찾아 해결하면 된다.
배포 전, 메모리 책임은 프로그래머의 미덕이지 않을까?
C_Memory.h
출처 : http://blog.naver.com/hermet?Redirect=Log&logNo=54353236