C++

[C++] Garbage Collection & Reference Counting

luk_hwkim 2022. 12. 7. 15:17

프로그래밍 언어엔 메모리를 프로그래머가 직접 관리해야하는 unmanaged 언어와 언어단에서 관리해주는 Managed 언어가 있다.

대표적인 예로 C/C++Unmanaged 언어이며 C#, JAVA가 Managed 언어이다

대부분의 Managed 언어에서는 메모리 관리를 하기 위해 다음중 하나의 기법을 사용한다.

  • Garbage collection
  • Reference counting

🔖Garbage Collection

메모리에 주소가 할당하여 사용중에 메모리 주소를 새로 할당하여 가리키는 주소값이 변경되거나 형변환이 되면서 주소를 잃어버리게 되고, 다시 찾을 수 없게 되는 메모리가 생겨버리게 되는데 이렇게 발생하는 가비지 메모리를 가비지 컬렉터(Garbage collector)가 추적하고 해제하게 된다.

가비지 컬렉션은 프로그램 실행 중에 일정 주기마다 또는 특정 조건일 경우(보통 메모리가 충분치 않을 경우)에 스택과 같이

변수가 저장되는 곳(root)를 훑으면서 사용되지 않는 메모리를 추적하여 삭제한다.

사용되지 않는 메모리의 기준은 root로부터 어떻게든 접근될 수 있냐 이다.

https://www.telerik.com/blogs/understanding-net-garbage-collection

Garbage Collection의 한계

  • 어떤 방식의 가비지 컬렉션을 사용하든 실행 시간에 작업을 하는 이상 성능 하락을 피할 수는 없다 (특정 주기 또는 조건에따라 한번에 가비지 컬렉팅을 수행하기 때문에)
  • 가비지 컬렉터가 존재하더라도 더 이상 접근이 불가능한 객체만 회수하기 때문에 메모리 누수는 발생할 수 있다

 

🔖Reference Counting

Reference counting의 동작 방식은 간단하다. 동적으로 할당된 메모리 주소가 참조될 때마다 count1씩 증가시키고, 참조를 끊을 땐 1 감소 시킴으로써 count를 체크하여 0이 될 경우 더 이상 참조되지 않는 메모리로 판단하고 즉시 메모리를 해제하는 방식이다.

 

대표적으로 사용하는 곳

  • Component Object Model(COM)
  • 마이크로소프트의 Iunknown, ATL
  • Perl, Python, Squirrel
  • C++ shared_ptr
  • Glib(Gobject), Apache APR

 

C/C++은 직접 메모리를 관리 해주어야 하기 때문에 레퍼런스 카운트를 아래와 같이 간단한 클래스로 구현하여 메모리 참조와 해제를 자동화 시킬 수 있다.

template<class _Ty>
class CAutoReference
{
public:
	// Default Constructor
	CAutoReference()
		:ptr_(nullptr), ref_count_(nullptr)
	{
		
	}
	// Constructor Direct Allocation
	CAutoReference(size_t alloc_counts)
	{
		ptr_	    = CEsayMemory::Alloc<_Ty>(alloc_counts);
		ref_count_  = CEsayMemory::Alloc<size_t>();
		*ref_count_ = 0;
		++(*ref_count_);
	}
	// Constructor Move Allocation
	CAutoReference(_Ty* ptr)
		:ptr_(ptr), ref_count_(nullptr)
	{
		if(nullptr == ref_count_)
		{
			ref_count_	 = CEsayMemory::Alloc<size_t>();
			*ref_count_  = 0;
		}
		++(*ref_count_);
	}
	// Copy Constructor
	CAutoReference(const CAutoReference& other)
		:ptr_(other.ptr_), ref_count_(other.ref_count_)
	{
		++(*ref_count_);
	}
	// Destructor
	~CAutoReference()
	{
		if (ref_count_)
		{
			if (0 == (--(*ref_count_)))
			{
				CEsayMemory::Free(ptr_);
				CEsayMemory::Free(ref_count_);
			}
		}
	}

	// Operator Overloading ==============================================
	CAutoReference& operator=(const CAutoReference& rhs)
	{
		if(this == &rhs)
		{
			return *this;
		}
		if(ptr_ == rhs.ptr_)
		{
			return *this;
		}
		this->~CAutoReference();

		++(*rhs.ref_count_);
		ptr_	   = rhs.ptr_;
		ref_count_ = rhs.ref_count_;

		return *this;
	}
	_Ty& operator*() const
	{
		return *ptr_;
	}
	_Ty* operator->() const
	{
		return ptr_;
	}
	_Ty& operator[](size_t index) const
	{
		return ptr_[index];
	}
	// ===================================================================
private:
	_Ty*	ptr_;
	size_t* ref_count_;
};