공부중
[C++]스마트포인터(Smartpointer) - shared_ptr 메모리 누수(memory leak)에 관련해서 본문
[C++]스마트포인터(Smartpointer) - shared_ptr 메모리 누수(memory leak)에 관련해서
곤란 2018. 5. 31. 18:02std::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 에대해서도 공부를 해봐야겠다...
'Programing > C, C++' 카테고리의 다른 글
[C++] 상속관계에서의 생성자 소멸자 호출 (0) | 2018.11.04 |
---|---|
[C++] 함수 오버로딩에서 적절한 파라미터를 못찾는다면? (0) | 2018.11.04 |
[C++]스마트포인터(Smartpointer) - shared_ptr (0) | 2018.05.30 |
[C++]스마트포인터(Smartpointer) - unique_ptr (0) | 2018.05.25 |
[C++]범위 기반 for문(Range-based for loop) (0) | 2018.05.25 |