본문 바로가기
개발, IT

Detected Memory leaks(메모리릭 찾기)

by Nabi™ 2008. 6. 18.

코드를 작성하다보면 new연산자를 이용해서 메모리를 동적으로 할당하여 사용하는 경우가 매우 흔하다. 이후 해제를 해주면 아무 문제가 없지만.. 이때 해제를 해주지 않았을 경우 디버거는 Output Window로 Memory leaks 라는 메시지를 출력한다.

즉, Memory leak란 메모리를 할당하여 사용하고 해제를 해주지 않았을때 발생하는 것인데..
아주 작은 프로그램이나 유닛테스트용 프로그램 이라면 크게 상관이 없지만, 대부분의 프로그램에서는 이것은 언제 터질지 모르는 폭탄과도 같은 것이다.

생각해봐라... 제품을 팔았는데, 어느날 갑자기 사용자가 연락이 와서..
"이거 자꾸 프로그램이 죽어버리는데요?" 라고 했을때...
백날 컴파일만 해도 에러 하나 뜨지 않는다.

지금 이 글을 쓰고 있는 주인장도 이런 미미한 실수로 인해서 얼마전에 온종일 고생한적이 있다.
자..그럼 본격적으로 Memory leak라는 녀석을 잡아보자.

작성한지 하루 이틀 된 소스나 간단한 소스라면 손쉽게 해결할 수 있다.

허나 기억도 가물가물하고 혹은 함수나 메소드가 매우 복잡하고 양까지 방대하다면 생각처럼 만만치 않다. 이럴때 사용하라고 제공되는 유틸리티 함수가 있다.  AfxSetAllocStop 이다.

조금 더 많은 기능을 제공하는 클래스가 있는데 그것은 아래에서 살펴보도록 하자.

AfxSetAllocStop(LONG lRequestNumber)
//인수 LONG lRequestNumber는 리퀘스트 번호를 지정한다.

임의로 메모리 리크가 발생하게 했을때 Output Window의 내용이다.
/**********************************************************************
Detected memory leaks!
Dumping objects ->
C:\Program Files\Microsoft Visual Studio\MyProjects\Bug\BugDlg.cpp(186) : {78} normal block at 0x00344E58, 3246 bytes long.
************************************************************************/

1행은 memory leak가 발생했음을 의미하고, 3행에서 그 위치를 알려준다.
BugDlg.cpp의 186번째 줄에서 memory leak가 발생했단다.

이 함수의 사용법은 매우 간단하다.
메모리 리크 메시지의 리퀘스트 번호({ } 사이에 표시되는 번호)를 인수로 사용한다.
이 리퀘스트 번호는 실행시 Output Wondow에 표시된다.
그리고 메모리 리크가 발생한 위치에서 AfxSetAllocStop 함수를 호출한다.

여기서는 리퀘스트 번호가 78이므로

AfxSetAllocStop(78);

이렇게 호출해주면 된다.

코드 수정이 끝나면 빌드를 끝내고 디버거를 기동한다.

그러면 지정하거나 리퀘스트 번호에 대응하는 메모리의 확보가 시행된 순간에 마치 브레이크 포인트를 발견하도록 실행을 정지시켜, 디버거로 조사를 시작할 수 있다. 이것을 사용하면, 메모리 리크가 발생한 순간에 변수의 값이나 콜 스택을 조사할 수 있다.

작업이 끝나면 AfxSetAllocStop함수는 삭제하도록 한다.

그런데 AfxSetAllocStop함수는 완전하게 재현성이 있는 경우에만 도움이 된다.

즉, 실행할 때마다 메모리 리크가 발생하는 리퀘스트 번호가 다른 경우에는 의미가 없다는 것이다.

메모리 리크의 판단은 단지 라이브러리가 판단하지 못하는 것이지, 프로그래머에게는 해당되지 않는 말이다. 프로그래머는 소스를 분석하여 특정 영역에서 new연상자와 delete연산자의 대응이 완전하게 닫혀 있는 것을 판단할 수 있다. (바보가 아닌 이상...)

즉, new와 delete가 닫혀 있는 특정 소스 코드행의 영역을 설정하고, 이 영역에 들어갈 때의 메모리 블록의 수와 그 영역에서 나왔을 때의 메모리 블록의 수를 조사하고, 이것이 일치하지 않으면 메모리 리크가 발생하고 있다고 생각할 수 있다.

이와 같이 어느 시점에서 스냅 숏(메모리의 상태)을 파악하여 메모리 리크를 검출하는 클래스가 있는데, CMemoryState 클래스이다.

이 클래스의 주요 메소드를 살펴보면..

void CMomoryState::Checkpoint() //스냅 숏을 갖는다.

BOOL CMomoryState::Difference(const CMomoryState& oldState, const CMomoryState& newState) // 2개의 스냅 숏의 차이를 평가한다. 차이가 있으면 TRUE를 반환한다.

void CMomoryState::DumpStatistics() // 스냅 숏의 내용을 표시한다.

간략하게 예를 보자.
/******************************************************************************
CMomoryState begin, end, diff;
begin.Checkpoint(); //이 시점에서의 메모리 확보 상황의 스냅 숏을 begin으로 한다.

//////////////////////////////////////////////////////
// 여기에 new와 delete의 관계가 열려 있는 것으로 하자. //
//////////////////////////////////////////////////////

end.Checkpoint(); //이 시점에서의 스냅 숏을 end로 한다.
if(diff.Difference(begin, end)){ //차이가 있다면 TRUE이므로 if문이 실행된다.
diff.DumpStatistics(); //차이를 표시한다.
}
*******************************************************************************/

이렇게 했을때 이 차이가 Output Window에 표시되는데, 이것을 살핍으로써 몇개의 메모리 블록이 리크하고 있는지, 그리고 그 크기는 어느 정도인지 알 수 있다.

이 차이를 보고할때는 5종류의 블록으로 보고되는데..각각의 의미는 아래와 같다.

Free Blocks : 해제된 메모리 블록.
Normal Blocks : char나 int와 같은 기본형이나,
         CObject 클래스를 계승하지 않은 클래스를 위해 확보된 메모리 블록.
CRT Blocks : C랜덤 라이브러리 안에서 확보된 메모리 블록.
Igoner Blocks : 메모리 리크 검출 기구를 오프로 하고 있을 때에 확보된 메모리 블록.
Client Blocks : CObject 클래스 및 그 파생의 개체를 위해 확보된 메모리 블록.

여기서 Free Bolcks만 다른데..Free Blocks는 해제가 끝난 블록인데 리스트로 되어 있는 것이다.

단, 이 기능은 디폴트에서는 무효화된다. 이 기능을 유효화하기 위해서는 MFC내부에서 정의되어 있는 글로벌 변수 afxMemDf에 delayFreeMemDf를 설정해야 한다.

구체적으로 다음과 같은 코드가 실행되면 행 이후에 기능을 수행하게 된다.

afxMemDf |= delayFreeMemDf;

//논리합으로 대입한 것은 afxMemDf에는 그 외의 메모리 관리용 플래그도 설정되었기 때문이다.

이 기능을 이용하면 delete연산자나 free함수에 의해 메모리를 해제시켜도, 그 메모리 블록이 재이용되지 않는다. 즉, 해제하였을 때의 데이터가 그대로 메모리에 남기 때문에, 할당되어 있던 메모리의 내용을 체크할 수 있다. 허나 이럴경우 부작용이 있을 수 있는데..시스템이 메모리가 부족하게 되기 쉽상이다. 보통 해제된 메모리는 다음 메모리 확보 요구에 의해서 재이용되지만, 이것이 실행되지 않기 때문에 잠시 지나면 메모리가 부족하게 되는 것이다. 이것을 이용하여 의도적으로 메모리에 스트레스를 주는 테스트에 이용할 수도 있을 것이다.

좋은정보가 되셨다면 아래 한번 클릭해주세요^^


댓글