🧼C, C++/기초및 입문

[C++/MSVC] 디버깅(Debugging) 강좌 (메모리 누수 방지)

Mawile 2021. 11. 25.
728x90

해당강좌는 미완성강좌입니다.

완성될때까지 북마크하고 기다려주세요!

 


 

디버깅(Debugging) 강좌

안녕하세요!

이번 포스팅에서는 MSVC컴파일러에서 지원하는 디버깅 기능을 적극적으로 이용하여 저희가 직접 메모리에서 생겨나는 누수들을 탐지하고 디버그하는 내용을 알려드리겠습니다!

 

pinterest

 

 

우선 시작하기 앞서서 목차를 확인하고 가시죠!

목차
  • 1-1. Visual Studio 2022 Current (v143) 디버그 하는방법
  • 1-2. 메모리 누수가 위험한 이유
  • 2-1. _CrtDumpMemoryLeaks : 메모리누수를 방지하는 간단한 방법
  • 2-2. _CrtSetReportMode : 메모리누수 보고를 설정하는 방법
  • 2-3. 메모리누수를 탐지하고 통지하는 기능 만들기
  • 2-4. MyCheckMemoryLeaks 클래스 만들기
  • 3-1. _CrtMemState 메모리 상태 비교
  • 3-2. MyCheckMemoryLeaks 클래스에 누수된 메모리의 크기를 확인하는 기능 만들기

 

 

1-1. Visual Studio 2022 Current (v143) 디버그 하는방법

비주얼스튜디오 2022 에서 디버그를 하는 방법은 매우 간단합니다.

우선 C++프로젝트에 들어가서 "로컬 Windows 디버거" 버튼을 누르거나, F5 버튼을 눌러주면 됩니다.

저는 F5를 누르는 방법이 간단하기때문에 이 방법을 많이 씁니다.

 

참고로 저는 C++17버전을 사용했습니다.

로컬 Windows 디버거을 클릭하거나 F5버튼을 클릭하세요!

 

 

1-2. 메모리 누수가 위험한 이유

메모리 누수는 쉽게 말해서 프로그램이 운영체제로부터 가져온 메모리공간을 프로그램이 켜져있는 동안 계속해서 쓸데없는 메모리공간을 남겨두거나 늘리는것을 말합니다.

 

우리가 C/C++프로그래머라면 메모리관리는 필수라고 보면됩니다.

요즘나오는 컴퓨터들은 대부분 사양이 좋기때문에 작은 프로그램에서는 크게 별 문제는 없습니다.

하지만, 프로그램의 크기가 커지거나, 메모리관리가 안되어있는 프로그램을 장시간 켜두게되면 매우 느려집니다.

따라서 이러한 메모리누수에 관한 고민은 C/C++프로그래머들에게는 중요한문제이죠.

 

 

2-1. _CrtDumpMemoryLeaks : 메모리누수를 방지하는 간단한 방법

MSVC C++에서는 메모리누수를 쉽게 확인하기위한 차원에서 여러가지 레퍼런스들이 구현되어 있습니다.

우선 제일 처음에는 간단하게 메모리누수를 확인하기 위해 _CrtDumpMemoryLeaks 라는 함수를 사용해보겠습니다.

 

이 함수는 이름 그대로 메모리누수에 대한 내용을 덤프(Dump)합니다.

#include <iostream>
#include <cstdlib>

#include <crtdbg.h>

int main(int argc, char** argv) {
	int* _ = new int; // 메모리 누수 발생!

	int result = _CrtDumpMemoryLeaks();
	if (result != 0) {
		std::cout << "메모리 누수 발생!" << std::endl;
	}

	return 0;
}

 

실행해보면...

 

다음과같이 "Detected memory leaks!(메모리누수가 탐지됬습니다!)" 라고 디버그창에서도 알려줍니다.

그리고 뒤에 몇 바이트의 메모리가 누수되고있는지도 나옵니다(4 bytes long).

여기까지는 그냥 printf하는 느낌이기때문에 딱히 어려울건없습니다. 바로 다음으로 넘어가시죠!

 

 

2-2. _CrtSetReportMode : 메모리누수 보고를 설정하는 방법

이제 메모리누수를 보고할때 보고형식을 설정하는 방법에 관하여 설명드리겠습니다.

일반 메모리누수를 보고할때 비주얼스튜디오는 기본적으로 "디버깅창" 에다가 표시합니다.

이것을 윈도우창으로 바꾼다든지, 파일에 표시한다든지로 설정할 수 있습니다!

#include <iostream>
#include <cstdlib>

#include <crtdbg.h>

int main(int argc, char** argv) {
	// * _CRTDBG_MODE_DEBUG: 디버깅창에 표시
	// * _CRTDBG_MODE_FILE: 파일로 표시
	// * _CRTDBG_MODE_WNDW: 윈도우창으로 표시
	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_WNDW);

	int* _ = new int; // 메모리 누수 발생!

	int result = _CrtDumpMemoryLeaks();
	if (result != 0) {
		std::cout << "메모리 누수 발생!" << std::endl;
	}

	return 0;
}

 

실행하면...

 

다음과 같이 윈도우창으로 뜹니다!

와우 ! 굿!

여기까지 잘 따라오셨으면 바로 다음으로 넘어갑시다!

 

 

2-3. 메모리누수를 탐지하고 통지하는 기능 만들기

우선 우리는 메모리누수를 쉽게 탐지하고 통지하기 위해서 이러한 역할을 하는 함수를 따로 만들어주고 싶습니다..

그리고 메모리누수에 대한 탐지가 끝나고 어떤 정보를 통지할지 그 정보들을 저장하는 구조체를 정의해줍시다.

template <class rtnType>
struct _DBG_OBJ {
	rtnType data{0}; // 메모리누수 검사를 맞힌 함수의 반환값.
	bool isMemoryLeaks{false}; // 메모리누수가 생겼는지 여부
};

 

위 구조체는 이제 우리가 앞으로 정의할 메모리누수 탐지기능을 담당하는 함수의 반환값이 됩니다.

이제 메모리누수를 탐지하는 함수를 본격적으로 정의합시다.

void SetDbgMode() {
	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
}

template<class rtnType, class... paramType>
constexpr auto CheckMemoryLeaks(rtnType (*fn)(paramType...), paramType&&... param) {
	SetDbgMode();
	
	if constexpr (std::is_void_v<rtnType>) {
		_DBG_OBJ<int> dbgObj;
		fn(param...);
		dbgObj.data = -1;
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
	else {
		_DBG_OBJ<rtnType> dbgObj;
		dbgObj.data = fn(param...);
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
}

 

여기서는 클래스 템플릿이 사용되었습니다. 왜냐하면 모든 타입의 함수에 대응시키기 위해서입니다.

rtnType는 메모리누수를 검사할 함수의 반환타입입니다.

paramType은 메모리누수를 검사할 함수의 파라미터타입들입니다.

 

우리가 앞서 정의한 CheckMemoryLeaks함수의 첫번째 인자는 템플릿화된 함수포인터로 받습니다.

두번째 인자는 파라미터들의 가변인수들을 담게됩니다.

 

이 함수는 제일 처음으로 SetDbgMode함수를 호출하여 디버깅모드를 설정합니다.

그 다음 메모리누수를 탐지할 함수의 반환타입이 void형인지를 검사합니다.

여기서 void형인지 검사하는 이유는 C/C++에서는 void 라는 데이터타입을 그냥쓰면 컴파일오류가 발생합니다.

 

그래서 void형이면 int형으로 바꾸고 반환값을 -1로 바꿉니다.

void형이 아니라면 그대로 반환값을 구조체에 집어넣습니다.

 

이제 main 함수까지 구현해주면 풀 코드는 다음과 같이 생기게 됩니다.

#include <iostream>
#include <cstdlib>

#include <crtdbg.h>

#define new new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )

template <class rtnType>
struct _DBG_OBJ {
	rtnType data{ 0 };
	bool isMemoryLeaks{ false };
};

void SetDbgMode() {
	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
}

template<class rtnType, class... paramType>
constexpr auto CheckMemoryLeaks(rtnType(*fn)(paramType...), paramType&&... param) {
	SetDbgMode();

	if constexpr (std::is_void_v<rtnType>) {
		_DBG_OBJ<int> dbgObj;
		fn(param...);
		dbgObj.data = -1;
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
	else {
		_DBG_OBJ<rtnType> dbgObj;
		dbgObj.data = fn(param...);
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
}

// 메모리누수가 발생하는 함수
int memory_leaks_true_function(int i1, int i2) {
	int* _ = new int;
	*_ = i1 + i2;

	int result = *_;
	return result;
}

// 메모리누수가 발생하지 않는 함수
int memory_leaks_false_function(int i1, int i2) {
	int result = i1 + i2;
	return result;
}

int main(int argc, char** argv) {
	auto dbgObj1 = CheckMemoryLeaks<int, int, int>(memory_leaks_false_function, 1, 2);
	std::cout << "memory_leaks_false_function 반환값: " << dbgObj1.data << '\n';
	std::cout << "memory_leaks_false_function 메모리누수 발생여부: " << std::boolalpha << dbgObj1.isMemoryLeaks << '\n';

	auto dbgObj2 = CheckMemoryLeaks<int, int, int>(memory_leaks_true_function, 1, 2);
	std::cout << "memory_leaks_true_function 반환값: " << dbgObj2.data << '\n';
	std::cout << "memory_leaks_true_function 메모리누수 발생여부: " << std::boolalpha << dbgObj2.isMemoryLeaks << '\n';

	return 0;
}

 

이해가셨나요?? 그럼 바로 다음으로 넘어가죠!!

 

 

2-4. MyCheckMemoryLeaks 클래스 만들기

흠...

위와 같이 최대한 깔끔하게 구현하고 싶어도..

함수프로그래밍에 있어서 디자인&기능적인 한계는 벗어나지 못했습니다..ㅠㅠ

하지만!!

객체프로그래밍을 이용해서 이와 같은 기능을 깔끔하게 클래스로 구현한다면?? 이야기가 달라집니다.

 

기본적인 프레임워크는 위에서 설명했기때문에 전에 설명한 내용들은 건너띄겠습니다!

2_4_MyCheckMemoryLeaks.h

먼저 헤더파일, 구조체먼저 선언하겠습니다.

이 내용은 앞에서 다뤘기때문에 추가설명없이 스킵!

#pragma once

#include <iostream>
#include <cstdlib>

#include <crtdbg.h>

#ifdef ReportDebuggingDetails
	#define new new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#endif

template <class rtnType>
struct _DBG_OBJ {
	rtnType data{ 0 };
	bool isMemoryLeaks{ false };
};

 

이제 클래스를 구현해봅시다!

template<class rtnType, class... paramType>
class MyCheckMemoryLeaks {
public:
	MyCheckMemoryLeaks();
	MyCheckMemoryLeaks(const MyCheckMemoryLeaks&);
	~MyCheckMemoryLeaks();

	void PushFunction(rtnType(*)(paramType...));
	void SetDbgFlag(int);
	auto CheckMemoryLeaks(paramType...);

private:
	void SetModeDebugging(int);

private:
	rtnType (*m_fn)(paramType...);
	int m_dbgFlag;

};

 

후...

우선 생성자, 파괴자를 선언하고 public함수 3개와 private함수 1개를 선언했습니다.

PushFunction 함수는 앞으로 우리가 메모리누수를 탐지할 함수를 클래스에 저장하는 함수입니다.

SetDbgFlag 함수는 메모리누수를 보고할때 보고할 방식에 대한 플래그를 설정하는 함수입니다.

CheckMemoryLeaks 함수가 이 클래스의 핵심입니다! 메모리누수를 탐지하는 함수이죠.

SetModeDebugging 함수는 SetDbgFlag 함수를 통해 받은 플래그를 실제로 디버깅에 적용하는 함수입니다!

 

이제 정의파트 가시죠!

template<class rtnType, class... paramType>
MyCheckMemoryLeaks<rtnType, paramType...>::MyCheckMemoryLeaks() {
	this->m_fn = nullptr;
	this->m_dbgFlag = 0x00;
}

template<class rtnType, class... paramType>
MyCheckMemoryLeaks<rtnType, paramType...>::MyCheckMemoryLeaks(const MyCheckMemoryLeaks<rtnType, paramType...>&) {
}

template<class rtnType, class... paramType>
MyCheckMemoryLeaks<rtnType, paramType...>::~MyCheckMemoryLeaks() {
}

template<class rtnType, class... paramType>
void MyCheckMemoryLeaks<rtnType, paramType...>::PushFunction(rtnType(*fn)(paramType...)) {
	this->m_fn = fn;
}

template<class rtnType, class... paramType>
void MyCheckMemoryLeaks<rtnType, paramType...>::SetDbgFlag(int flags) {
	this->m_dbgFlag = flags;
}

template<class rtnType, class... paramType>
auto MyCheckMemoryLeaks<rtnType, paramType...>::CheckMemoryLeaks(paramType... param) {
	this->SetDbgFlag(this->m_dbgFlag);

	if constexpr (std::is_void_v<rtnType>) {
		_DBG_OBJ<int> dbgObj;
		this->m_fn(param...);
		dbgObj.data = -1;
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
	else {
		_DBG_OBJ<rtnType> dbgObj;
		dbgObj.data = this->m_fn(param...);
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
}

template<class rtnType, class... paramType>
void MyCheckMemoryLeaks<rtnType, paramType...>::SetModeDebugging(int flags) {
	_CrtSetReportMode(_CRT_WARN, flags);
}

 

진짜 2-3내용과 비교해서 크게 달라진건 없네요!

헤더파일의 전체코드입니다!

#pragma once

#include <iostream>
#include <cstdlib>

#include <crtdbg.h>

#ifdef ReportDebuggingDetails
	#define new new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#endif

template <class rtnType>
struct _DBG_OBJ {
	rtnType data{ 0 };
	bool isMemoryLeaks{ false };
};

template<class rtnType, class... paramType>
class MyCheckMemoryLeaks {
public:
	MyCheckMemoryLeaks();
	MyCheckMemoryLeaks(const MyCheckMemoryLeaks&);
	~MyCheckMemoryLeaks();

	void PushFunction(rtnType(*)(paramType...));
	void SetDbgFlag(int);
	auto CheckMemoryLeaks(paramType...);

private:
	void SetModeDebugging(int);

private:
	rtnType (*m_fn)(paramType...);
	int m_dbgFlag;

};

template<class rtnType, class... paramType>
MyCheckMemoryLeaks<rtnType, paramType...>::MyCheckMemoryLeaks() {
	this->m_fn = nullptr;
	this->m_dbgFlag = 0x00;
}

template<class rtnType, class... paramType>
MyCheckMemoryLeaks<rtnType, paramType...>::MyCheckMemoryLeaks(const MyCheckMemoryLeaks<rtnType, paramType...>&) {
}

template<class rtnType, class... paramType>
MyCheckMemoryLeaks<rtnType, paramType...>::~MyCheckMemoryLeaks() {
}

template<class rtnType, class... paramType>
void MyCheckMemoryLeaks<rtnType, paramType...>::PushFunction(rtnType(*fn)(paramType...)) {
	this->m_fn = fn;
}

template<class rtnType, class... paramType>
void MyCheckMemoryLeaks<rtnType, paramType...>::SetDbgFlag(int flags) {
	this->m_dbgFlag = flags;
}

template<class rtnType, class... paramType>
auto MyCheckMemoryLeaks<rtnType, paramType...>::CheckMemoryLeaks(paramType... param) {
	this->SetDbgFlag(this->m_dbgFlag);

	if constexpr (std::is_void_v<rtnType>) {
		_DBG_OBJ<int> dbgObj;
		this->m_fn(param...);
		dbgObj.data = -1;
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
	else {
		_DBG_OBJ<rtnType> dbgObj;
		dbgObj.data = this->m_fn(param...);
		dbgObj.isMemoryLeaks = _CrtDumpMemoryLeaks();

		return dbgObj;
	}
}

template<class rtnType, class... paramType>
void MyCheckMemoryLeaks<rtnType, paramType...>::SetModeDebugging(int flags) {
	_CrtSetReportMode(_CRT_WARN, flags);
}

 

다음은 소스파일입니다!

2_4_MyCheckMemoryLeaks.cpp
#define ReportDebuggingDetails

#include "2_4_MyCheckMemoryLeaks.h"

// 메모리누수가 발생하는 함수
float memory_leaks_true_function(int i1, int i2) {
	float* _ = new float;
	*_ = i1 + i2 + 0.5f;

	float result = *_;
	return result;
}

// 메모리누수가 발생하지 않는 함수
float memory_leaks_false_function(int i1, int i2) {
	float result = i1 + i2 + 0.5f;
	return result;
}

int main(int argc, char** argv) {
	MyCheckMemoryLeaks<float, int, int> myCheckMemClass;
	myCheckMemClass.PushFunction(memory_leaks_false_function);
	myCheckMemClass.SetDbgFlag(_CRTDBG_MODE_DEBUG);
	auto result_false = myCheckMemClass.CheckMemoryLeaks(5, 3);

	std::cout << "memory_leaks_false_function 반환값: " << result_false.data << '\n';
	std::cout << "memory_leaks_false_function 메모리누수 발생여부: " << std::boolalpha << result_false.isMemoryLeaks << '\n';


	myCheckMemClass.PushFunction(memory_leaks_true_function);
	myCheckMemClass.SetDbgFlag(_CRTDBG_MODE_DEBUG);
	auto result_true = myCheckMemClass.CheckMemoryLeaks(5, 3);

	std::cout << "memory_leaks_true_function 반환값: " << result_true.data << '\n';
	std::cout << "memory_leaks_true_function 메모리누수 발생여부: " << std::boolalpha << result_true.isMemoryLeaks << '\n';

	return 0;
}

 

실행하면??

 

와우...

클래스를 사용하자마자... 코드도 너무 깔끔하고 아름다워졌습니다.

물론 기능도 정상작동합니다!!

 

 

3-1. _CrtMemState 메모리 상태 비교

미완성입니다!

뒷내용이 궁금하신분들은 포스팅이 완성될때까지 북마크하고 기다려주세요!

 

 


728x90

댓글