'메모리누수'에 해당되는 글 2건

  1. C코드 메모리릭 잡기
  2. 부모 클래스의 기본생성자가 없을때 메모리 누수 현상 2


프로그램을 개발하다보면 사람인지라 버그나 메모리릭이 생기는 것은 당연합니다.


참조: 네이버웹툰, 스마트폰 게임 개발 이야기에서...


따라서 이를 빠르게 발견하고 해결하는 것이 중요합니다.

여기서는 C코드로 짠(콘솔 응용프로그램) 환경에서 메모리릭을 발견하고 잡는 방법을 알아보겠습니다.

다음과 같이 메인 프로젝트를 만들고 메모리릭을 감지하는 코드를 추가합니다. (강조된 부분)

  
// MemoryLeak.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"

#ifdef _DEBUG
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	// 메모리릭 발생코드
	char * szName = new char[100];
	szName = "mooyou";
	printf("%s", szName);

	return 0;
}


위의 코드를 실행하면 출력창에 메모리릭이 발생되었다고 나타납니다.

출력 정보를 보면 memoryleak.cpp 파일의 18번째 라인에서 100 bytes 크기의 메모리릭이 발생되었습니다.

이 정보만으로도 충분히 메모리릭을 잡을수 있습니다.



여기서 더 나아가 메모리 블록 위치 정보를 통해서 직접적으로 메모리릭이 발생한 코드로 이동까지 해보겠습니다.

이를위해 다음과 같이 _CrtSetDbgFlag 바로아래 _CrtSetBreakAlloc( 97 ); 코드를 추가합니다.

이는 메모리릭 발생 위치에 BreakPoint를 설정합니다.

  
// MemoryLeak.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"

#ifdef _DEBUG
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	_CrtSetBreakAlloc( 97 );
#endif

	// 메모리릭 발생코드
	char * szName = new char[100];
	szName = "mooyou";
	printf("%s", szName);

	return 0;
}


즉, 출력된 메모리 블록 위치 정보를 이용하여 BreakPoint를 설정하고, 실행시 설정된 곳으로 이동하겠다라는 것입니다 !

코드를 추가하고 실행하면 다음과 같은 메시지 박스가 나타납니다.

BreakPoint에 의해 중단점을 트리거 했다라는 내용이고 여기서는 중단을 누르면 됩니다.

하지만 누르고 나서 코드를 보면 메모리릭이 발생한 원하는 위치가 아닌 dbgheap.c 파일의 내용이 보이게 됩니다.

모르는 코드화면이 나왔다고 해서 전혀 걱정할 필요가 없습니다. 정상적인 화면이고... 제대로 잘 찾아온 것입니다. 

메모리릭이 발생한 곳으로 이동하려면 호출 스택에서 작업위치를 찾아가면 됩니다.

호출 스택은 보통 출력창과 같은 탭에 있고, 없다면 Ctrl + Alt + C를 눌러 호출 스택을 불러옵니다.

호출 스택은 현재 프로그램에서 실행된 명령을 순차적으로 나열한 것으로 맨 위에 찍힌 스택이 최근에 사용한 명령입니다.

현재 보이는 위치(노란색 화살표)가 msvcr100.dll로 dbgheap.c의 내용을 가르킵니다.

즉, 작업한 코드 영역이 아니죠. 따라서 작업한 영역인 MemoryLeak 스택을 더블클릭하여 이동합니다.

짜잔~ 왼쪽에 초록색 화살표가 보이시나요?

호출 스택을 이용하여 메모리릭이 발생한 위치로 이동하였습니다.



C++로 개발되어진 모듈을 수정하던중 생각지도 않는 문제가 발생했습니다.


문제가 되는 기본 전제는 이러합니다. 부모의 기본생성자가 없다라는 점과 

자식 클래스에서는 이 부모 클래스를 상속받아 생성자를 오버라이딩해서 사용한다라는 점입니다.


여기서 부모 클래스 생성자에서는 메모리 할당을 하고 오버라이딩된 자식 클래스 생성자에서

이를 다시 메모리 할당하고자 할때 메모리 누수가 발생하였습니다. 지금 생각해도 참으로 복잡한 문제이죠...

에러가 난 코드를 최대한 간소화 시켜보았습니다.


//
// 메모리 누수 확인 코드
//
#ifdef _DEBUG
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

//
// Class A
//
class A {
public:
	char* szA;

public :
	A::A(int a)
	{
		szA = new char[a];

		OutputDebugString(_T("A생성자 호출\n"));
	}

	A::~A()
	{
		delete [] szA;

		OutputDebugString(_T("A소멸자 호출\n"));
	}
};

//
// Class B
//
class B : public A {

public:
	B::B(int b)
		// 1.
		// 부모가 기본생성자를 갖고있지 않으므로 자식은 생성자를 만들기 위해서
		// 부모의 사용가능한 생성자를 직접 지정해주어야 한다.
		: A(b)		
	{
		// 2.
		// 부모에서 생성된 객체를 메모리 할당
		szA = new char[b];

		OutputDebugString(_T("B생성자 호출\n"));
	}

	B::~B()
	{
		// 3.
		// 할당한 메모리를 제거하려 했으나 에러 발생
		// 여기서 메모리 누수 발생
		// delete [] szA;

		OutputDebugString(_T("B소멸자 호출\n"));
	}
};

//
// Main Function
//
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	A a(3);
	B b(3);	// 메모리 누수 발생

	return 0;
}


이를 해결하고자 다음과 같이 Class B를 수정하였습니다.

  
//
// Class B
//
class B : public A {

public:
	B::B(int b)
		// 1.
		// 부모가 기본생성자를 갖고있지 않으므로 자식은 생성자를 만들기 위해서
		// 부모의 사용가능한 생성자를 직접 지정해주어야 한다.
		: A(b)		
	{
		// 4.
		// 지정해준 [: A(b)] 부모 생성자에서 할당했던 객체를 미리 해제하고
		// 해당 생성자에서 할당된 객체는 이후 A소멸자가 호출될때 제거하게 해준다.
		delete [] szA;

		// 2.
		// 부모에서 생성된 객체를 메모리 할당
		szA = new char[b];

		OutputDebugString(_T("B생성자 호출\n"));
	}

	B::~B()
	{
		// 3.
		// 할당한 메모리를 제거하려 했으나 에러 발생
		// 여기서 메모리 누수 발생
		// delete [] szA;

		OutputDebugString(_T("B소멸자 호출\n"));
	}
};


'개발이야기 > C, C++' 카테고리의 다른 글

C언어 pthread 사용 방법  (0) 2014.03.05
포인터 배열 테스트 코드  (0) 2012.08.07
C코드 메모리릭 잡기  (0) 2012.07.18
마방진 원리 및 문제  (4) 2012.05.19
Mangled Name  (0) 2012.05.13