개요
이번 강의에서는 Unreal Engine의 가비지 컬렉션(Garbage Collection) 시스템을 이해하고, UObject 포인터를 안전하게 관리하는 방법을 학습합니다. C++의 고질적인 메모리 문제를 해결하는 Unreal의 자동 메모리 관리 시스템을 실습을 통해 체득합니다.
학습 목표
- Unreal Engine의 가비지 컬렉션 시스템 이해
- UObject 포인터의 안전한 관리 방법 학습
- 댕글링 포인터 문제 해결 방법 습득
- UPROPERTY의 중요성과 올바른 사용법 이해
- FGCObject를 사용한 고급 메모리 관리 기법 학습
C++ 메모리 관리의 고질적 문제
1. 메모리 누수
// ❌ 잘못된 예: 메모리 누수
void BadFunction()
{
MyClass* Object = new MyClass();
// delete를 호출하지 않음!
// Object가 가리키는 메모리는 영원히 회수되지 않음
}
// ✅ 올바른 예: 수동 관리
void GoodFunction()
{
MyClass* Object = new MyClass();
// ... 사용 ...
delete Object; // 반드시 해제
Object = nullptr;
}
문제점
- new로 할당한 메모리를 delete로 해제하지 않음
- 힙 메모리가 계속 쌓여 시스템 메모리 고갈
- 게임이 길어질수록 성능 저하
2. 댕글링 포인터
// ❌ 위험한 상황
MyClass* Ptr1 = new MyClass();
MyClass* Ptr2 = Ptr1; // 같은 객체를 가리킴
delete Ptr1; // Ptr1으로 메모리 해제
Ptr1 = nullptr;
// Ptr2는 여전히 해제된 메모리를 가리킴!
Ptr2->DoSomething(); // 💥 크래시!
문제점
- 다른 곳에서 메모리를 해제함
- 포인터는 여전히 해제된 주소를 가리킴
- 접근 시 크래시 또는 예측 불가능한 동작
3. 와일드 포인터
// ❌ 초기화하지 않은 포인터
void DangerousFunction()
{
MyClass* Ptr; // 초기화 안 함! (쓰레기 값)
if (SomeCondition)
{
Ptr = new MyClass();
}
// SomeCondition이 false면?
Ptr->DoSomething(); // 💥 임의의 메모리 접근!
}
// ✅ 안전한 방법
void SafeFunction()
{
MyClass* Ptr = nullptr; // 반드시 초기화
if (SomeCondition)
{
Ptr = new MyClass();
}
if (Ptr) // nullptr 체크
{
Ptr->DoSomething();
}
}
문제점
- 초기화하지 않은 포인터는 임의의 주소를 가리킴
- 운영체제 보호 영역 접근 시 크래시
- 디버그 어려움
가비지 컬렉션 시스템
개념
: 가비치 컬렉션(GC)은 프로그램에서 더 이상 사용하지 않는 객체를 자동으로 감지하여 메모리를 회수하는 시스템입니다.
도입 배경
- Java, C# 등 현대 언어들이 채택
- 메모리 안전성 향상
- 개발자 실수 방지
- 생산성 증대
Mark-and-Sweep 알고리즘
: Unreal Engine이 사용하는 가비지 컬렉션 방식입니다.
1단계 : Mark (표시)
Root Objects (시작점) :
- GameInstance
- World
- PlayerController
- 기타 RootSet 객체
2단계 : 참조 추적
모든 Root에서 참조로 도달 가능한 객체들에 Mark 플래그 설정
3단계 : Sweep (정리)
GUObjectArray 전체 스캔 :
- Mark가 되어 있지 않으면 가비지로 판정하여 메모리 회수
메모리 회수 방지 방법
1. UPROPERTY (가장 일반적)
UCLASS()
class UMyClass : public UObject
{
GENERATED_BODY()
public:
// ✅ UPROPERTY로 보호
UPROPERTY()
UTexture2D* Texture;
// ✅ TObjectPtr 사용 (UE5)
UPROPERTY()
TObjectPtr<UTexture2D> TexturePtr;
// ❌ UPROPERTY 없음 → GC에 수집됨!
UTexture2D* UnprotectedTexture;
};
2. FGCObject 상속
UCLASS()
class UMyManager : public FGCObject
{
GENERATED_BODY()
private:
// UPROPERTY 사용 불가한 특수 상황
TArray<UObject*> SpecialObjects;
public:
// GC에 수동으로 참조 알림
virtual void AddReferencedObjects(
UObject* InThis,
FReferenceCollector& Collector
) override
{
Super::AddReferencedObjects(InThis, Collector);
// 배열의 모든 객체를 참조로 등록
Collector.AddReferencedObjects(SpecialObjects);
}
virtual FString GetReferencerName() const override
{
return TEXT("FMyManager");
}
};
FGCObject 클래스를 상속 받아서 AddReferencedObjects, GetReferencerName을 구현하는 방법입니다.
3. RootSet 등록
void CreatePersistentObject()
{
UMyObject* PersistentObject = NewObject<UMyObject>();
// RootSet에 추가 → 절대 회수 안 됨
PersistentObject->AddToRoot();
// 나중에 제거 가능
// PersistentObject->RemoveFromRoot();
}
UObject 유효성 검사
UMyObject* MyObject = /* ... */;
// 1. IsValid (일반적 사용)
if (IsValid(MyObject))
{
MyObject->DoSomething();
}
// 2. IsValidLowLevel (저수준 검사) PendingKill 검사는 하지 않음
if (MyObject && MyObject->IsValidLowLevel())
{
MyObject->DoSomething();
}
// 3. nullptr 체크만 (불충분!)
if (MyObject != nullptr) // ⚠️ 댕글링 포인터 감지 못 함!
{
MyObject->DoSomething();
}'기타 > [강의] 이득우의 언리얼 프로그래밍 Part1' 카테고리의 다른 글
| [이득우의 언리얼] 11. 언리얼 컨테이너 라이브러리 - 구조체, Map (0) | 2026.02.08 |
|---|---|
| [이득우의 언리얼] 10. 언리얼 컨테이너 라이브러리 - TArray, TSet (0) | 2026.02.07 |
| [이득우의 언리얼] 9. 언리얼 C++ 설계 - 델리게이트 (0) | 2026.02.06 |
| [이득우의 언리얼] 8. 언리얼의 C++ 설계 - 컴포지션 (0) | 2026.02.06 |
| [이득우의 언리얼] 7. 언리얼 C++ 설계 - 인터페이스 (0) | 2026.02.04 |