본문 바로가기
Game DevTip/Unreal Engine

2. 언리얼 엔진의 메모리 관리에 대해서

by LIKE IT.라이킷 2024. 12. 5.

 




*해당 글은 공부용으로, C++를 사용한 언리얼 엔진 4개발 2/e의 내용이 다수 포함 되었습니다.

 

이번에 언리얼 엔진의 메모리 관리에 대해서 한번 알아보겠다.

 

일단 기본적으로 언리얼은 C++을 사용중인다. 

때문에 C에서 사용하던 malloc과 free보다는 new와 delete를 사용하는게 원칙이다.

(사실 원칙이라기 보다는 권장이다.)

 

new 연산자는 메모리 할당후 오브젝트가 생성된 직후에 생성자를 호출한다.

하지만 malloc에서는 이것이 되지 않는다. (물론 다른 기능들 자체는 malloc과 거의 동일.)

 

그리고 무조건 new 연산자를 사용하여 동적할당을 했다면, 

free()가 아닌 delete로 동적할당을 해제해야한다.

 

아무튼 new는 사용할 연속적인 메모리 공간을 할당하고,

생성자를 new연산자의 인수로 사용되는 오브젝트 타입으로 호출한다.

class Object
{
	Object()
    {
    	puts("Object constructed");
    }
    
    ~Object()
    {
    	puts("Object destructed");
	}   
};

//생성자 호출
Object * objectr = new Object();

//파괴자 호출
delete object;

//오브젝트를 null포인터로 초기화.
object = 0;
//또는 object = nullptr

 

만일 new 연산자를 사용한 대상의 타입이 오브젝트라면

생성자를 자동으로 호출한다. (malloc은 안됨.)

 

그렇다면 이제 언리얼에서 메모리를 동적할당하여 관리하는 법을 알아보자.

 


 

언리얼 엔진에서 관리되는 메모리는 다음으로 표현된다.

 

<indexenty content = "managed memory:allocating, NewObject used">

 

C++의 new, delete, free 호출의 상위에 존재하는 프로그래밍된 서브시스템에 의해서

할당과 해제가 이뤄지는 형태를 말하는 것이다. 

 

이 서브시스템은 일반적으로 프로그래머가 메모리를 할당한 후 해제하는 것을 잊지 않도록 작성된다.

즉, 메모리 누수를 방지 하는 것이다.

 

이것을 가비지 컬렉션이라고 부른다.(C#에는 리플렉션과 함께 포함되어 있는 기능이다.)

 

물론 그렇다고 해서 우리가 언리얼 스크립트를 작성하면서

저 위에 서브시스템의 코드를 직접 입력을 하는 일은 없을 것이다.

 

언리얼 엔진에서는 엔진내에서 사용하는 모든 오브젝트의 메모리 할당은 아래 함수를 사용하여 사용해야한다.

//1. UObject 파생 오브젝트를 생성시에 아래 코드 사용.
NewObject<>

//2. Actor이거나 그 파생 클래스일 경우 아래 코드 사용.
SpawnActor<>

 

 

자 그렇다면 실습을 해보자.

 


 

만약 UObject에서 파생된 UAction 타입의 오브젝트를 생성하려는 상황이 있다고 가정하고서

그렇다면 UAction을 생성해야하는 클래스에서는 어떻게 스크립트를 작성해야할까?

 

답은 아래 코드에 있다.

UAction * action = NewObject<UAction>(GetTransientPackage(), UAction::StaticClass());

 

여기서 UAction::StaticClass()를 사용하면 UAction 오브젝트의 베이스인

UClass* 를 얻을 수 있다. 

 

NewObject<>의 첫 인자는 GetTransientPackage()로 단순히 게임의 휘발성 패키지를 얻어오는데,

언리얼 엔진에서의 패키지는 단순한 데이터의 집합체이다.

여기서는 힙 메모리에 저장되는 데이터를 저장하고자 휘발성 패키지를 사용하였다.

 

UClass 인스턴스를 선택하기 위해서 블루프린트에서 아래 코드를 사용 할 수도 있었다.

UPROPERTY() 
	TSubclassOf<AActor>

 

또 하나 설명할 것은, 사실 나는 NewObject<>보다는 ConstructObject<>를 더 많이 쓰긴 한다.

ConstructObject<>는 NewObject<>보다 생성 시점에 조금 더 많은 파라미터를 제공한다.

 

특정 속성을 초기화 하고 싶은 것이 있다면, ConstructObject<>를 쓰길 바란다.


 

그렇다면 메모리 해제는 언리얼 엔진에서 어떻게 이뤄질까?

 

UObject 인스턴스는 참조 카운트를 지원해서 모든 참조가 사라지면 가비지 컬렉션을 자동으로 하도록 유도한다.

물론 수동으로 UObject::ConditionalBeginDestroy() 멤버 함수를 호출해서 수동으로도 메모리에서 해제가 가능하다.

 

ConditionalBeginDestroy() 함수는 아래와 같이 쓰여진다.

UObject *object = NewObject<UObject>(...);
object -> ConditionalBeginDestroy();

 

 

해당 개념은 모든 UObject 파생 클래스에 적용이 가능하다.,

이전에 UAction 오브젝트를 미리 해제 하고 싶다면 위에 예제 코드에서 한줄만 추가 하면 된다.

 

action-> ConditionalBeginDestroy();

 

ConditionalBeginDestroy() 명령은 메모리 해제절차를 시작하고,

재정의 가능한 BeginDestroy()와 FinishDestory() 함수를 호출한다.

 

물론 여전히 다른 클래스에서 해당 오브젝트를 참조중인데 해제를 하는 그런 짓은 피해야 하므로

신중하게 써야할 UObject의 멤버 함수들이다.

 

이번편은 여기까지이다.

반응형

댓글