공부중

[C++]스마트포인터(Smartpointer) - shared_ptr 메모리 누수(memory leak)에 관련해서 본문

Programing/C, C++

[C++]스마트포인터(Smartpointer) - shared_ptr 메모리 누수(memory leak)에 관련해서

곤란 2018. 5. 31. 18:02
반응형

std::shared_ptr은 레퍼런스 카운팅 방식의 스마트 포인터로.

특정 주소를 가리키는 포인터변수가 얼마나 있느냐에 따라서 자동으로 삭제할지 결정해서 자동으로 메모리 해제하는 방식이다.(count가 0일때 메모리 할당 해제)

 

이 레퍼런스 카운팅 방식의 스마트 포인터 방식은 특정 조건(?)에 따라서 메모리 누수(memory leak)이 발생 할 수가 있는데.

그것에 관련된 글을 써보려고 한다

 

일단 테스트 하는 코드는 아래와 같다.

#include <iostream>
#include <memory>

struct MyStruct
{
	std::shared_ptr<MyStruct> foo;
};

int main()
{
	//_CrtSetBreakAlloc(153);

	auto obj1 = std::make_shared<MyStruct>();
	auto obj2 = std::make_shared<MyStruct>();

	std::shared_ptr<MyStruct> obj3 = std::shared_ptr<MyStruct>(new MyStruct());
	std::shared_ptr<MyStruct> obj4 = std::shared_ptr<MyStruct>(new MyStruct());


	std::shared_ptr<MyStruct> obj5(new MyStruct());
	std::shared_ptr<MyStruct> obj6(new MyStruct());

	obj2->foo = obj1;
	obj1->foo = obj2;

	obj3->foo = obj4;
	obj4->foo = obj3;

	obj5->foo = obj6;
	obj6->foo = obj5;

	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	return 0;
}

위의 코드를 보면 정말 간단한 구조체(struct MyStruct)가 있고 그 구조체를 가지고 서로가 서로를 가리키는 순환 구조를 만들게 되면

레퍼런스 카운팅 방식의 스마트포인터는 메모리 누수가 난다.

 

이 메모리 누수를 해결하기 위해서는...

1. 그냥 이렇게 안짜면 된다.

2. weak_ptr이라는것을 이용한다.

 

뭐 이렇게 있는데...

어제 이거 관련해서 공부하다가 의문의 메모리 누수를 발견하였다.

 

코드를 보면 _CrtSetDbgFlag를 통해서 메모리 누수를 체크 하고 있는데

메모리 누수나는 크기가 다 달랐다(읭?)

 

테스트한 컴파일러는 VisualStudio 2017 community(15.2 version) 에서 x86으로 누수체크를 하였다.

	//1번
	auto obj1 = std::make_shared<MyStruct>();
	auto obj2 = std::make_shared<MyStruct>();

	//2번
	std::shared_ptr<MyStruct> obj3 = std::shared_ptr<MyStruct>(new MyStruct());
	std::shared_ptr<MyStruct> obj4 = std::shared_ptr<MyStruct>(new MyStruct());

	//3번
	std::shared_ptr<MyStruct> obj5(new MyStruct());
	std::shared_ptr<MyStruct> obj6(new MyStruct());

	//1번
	obj2->foo = obj1;
	obj1->foo = obj2;

	//2번
	obj3->foo = obj4;
	obj4->foo = obj3;

	//3번
	obj5->foo = obj6;
	obj6->foo = obj5;

중간에 있는 위의 코드를 가지고 하나씩 얼마나 누수가 되는지 확인을 해보자.

 

//1번 auto obj1 = std::make_shared<MyStruct>(); auto obj2 = std::make_shared<MyStruct>(); obj2->foo = obj1; obj1->foo = obj2;

 

먼저 1번의 누수 결과이다.

!? 생뚱맞은 20byte가 누수되고 있다.

저 20byte가 나올려면 4 * 5 = 20 이라는 결과밖에 안떠올라서 멍한이 어디서 어떤놈이 내부에서 만들어지는가 궁금해졌었고

sizeof로 obj1이나 obj2, 그리고 MyStruct 해보았자 8byte뿐... 8byte만으로는 어떻게 20이라는 숫자가 굴러들어왔는지 의문이였기 때문에...

 

어제 머리를 싸매고 고민을 했었다. ( -_- 이 글을 적게된 사건의 계기였다 )

 

아무튼 20byte씩 2개가 새고 있었다.

 

다음을 보자...

 

	//2번
	std::shared_ptr<MyStruct> obj3 = std::shared_ptr<MyStruct>(new MyStruct());
	std::shared_ptr<MyStruct> obj4 = std::shared_ptr<MyStruct>(new MyStruct());

	obj3->foo = obj4;
	obj4->foo = obj3;

 

2번의 누수 결과이다. 

이번에는 8byte + 16byte씩 2번 누수가 되었다.

 

다음마지막을 보자.

	//3번
	std::shared_ptr<MyStruct> obj5(new MyStruct());
	std::shared_ptr<MyStruct> obj6(new MyStruct());

	obj5->foo = obj6;
	obj6->foo = obj5;

 

이번 결과도 살펴 보면...

2번과 똑같이 8 + 16으로 누수가 나고 있다.

 

여기서 유추해볼수 있는것은 make_shared로 만든것은 x86에서 20씩 누수가 발견되었고

new 로 만든것은 8 + 16씩 누수가 발견되었다.

 

왜 그럴까? 저놈의 20은 어디서 굴러먹다온놈일까 -_-

저 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 의 버그인가 뭘까 생각을 해보며

어제 한참을 구글링해보고 찾아보고 디버깅 해보고 삽질해본 결과

정확한 답은 모르겠지만 일단 유추해본것을 여기에 적어보려고 한다 ㅂㄷㅂㄷ ( C++ 고오오오수님들중 아시는분 있으면 알려주세요 ㅠㅠ )

 

먼저 1번의 디버깅 조사식을 살펴보면...

형식들을 보면 내가 보지 못한 형식인 std::_Ref_count_base와 std::_Ref_count_obj<MyStruct> 가 보인다.

 

저것들은 control block 이라는곳에 들어가고 저 형식의 크기를 살펴보기로 했다.

 

직접 실행해보니 std::_Ref_count_base는 12 byte이고 std::_Ref_count_obj<MyStruct>는 20byte가 나온다.

드디어 의문의 20byte가 어디서 굴러들어왔는지 유추가 된다.

확실한거는 아니지만

1. std::_Ref_count_base가 12byte이고 위의 ptr이 8byte니까 둘이 더해서 20byte로 누수가 난것이다

2. std::_Ref_count_obj<MyStruct> 가 20byte니까 저 하나만 누수가 난거다.

3. 이게 맞는지는 모르겠지만 std::_Ref_count_obj<MyStruct>는 std::_Ref_count_base와 ptr이 합쳐진거를 가리키는것이다.

 

생각나는건 이 3가지 인데 뭐가 정답인지 모르니 일단 적어놓고 더 구글링을 해보고 공부를 해봐야 겠다.

 

이제 나머지들도 조사식을 살펴보면...

아까의 조사식과는 다른 모습이 보인다.

일단 control block 값이 make_shared에서 default로 바뀌었고..

형식은 std::_Ref_count_base{std::_Ref_count_obj<MyStruct>} 에서 std::_Ref_count_base{std::_Ref_count<MyStruct>}로 바뀌었다.

다른점이 있으니 sizeof로 크기를 재 보았다.

 

std::_Ref_count_base는 make_shared로 만들거나 new로 만드나 가변적인 크기가 아님을 알수 있고

new로 만들었을시 std::_Ref_count<MyStruct>형으로 만들어지며 이것은 16byte이다.

 

아까 위에서 new로 만들었을때는 8 + 16씩 누수가 발생하는 모습을 보여주었다.

 

위에서 유추한것을 다시 정리해서 유추해보면...

make_shared로 만들어서 누수가 발생시에는 std::_Ref_count_obj<typename> 만큼 누수가 발생하고

new로 만들어서 누수가 발생할 경우에는 ptr와 std::_Ref_count<typename>이 같이 누수가 발생한다.

 

라고 유추를 해보았다.

 

내 유추가 정답은 아니지만 이러한 내용을 적는것은....

호오오옥시나 C++ 고오오오수님이 저좀... 알려... 주세요.... ㅠㅠ

그리고 이것은 제 추측일 뿐이므로 정답이 아닙니다. ㄹㅇ....

 

이것에 관해서 더욱더 정확한 정답을 알게되면 따로 글을 쓰던지 이 글을 수정하든지 해서 적어놓을 예정입니다.

(이러고 ... 잊어버리는....건.... 후우 ㅠㅠ...)

 

그리고 control block 에대해서도 공부를 해봐야겠다...

 

 

반응형