득이공간

[언리얼 C++의 이해] 3장. 언리얼 엔진의 자료구조와 메모리 관리 본문

GP/UE5

[언리얼 C++의 이해] 3장. 언리얼 엔진의 자료구조와 메모리 관리

쟁득 2024. 1. 29. 20:57
해당 게시물은 이득우 교수님의 '언리얼 C++의 이해' 강의를 수강하며
학습한 내용을 개인적으로 정리한 글입니다.

📌 목차 - 3장. 언리얼 엔진의 자료구조와 메모리 관리

3-1. 언리얼 컨테이너 라이브러리 1 - Array와 Set
3-2. 언리얼 컨테이너 라이브러리 2 - 구조체와 Map
3-3. 언리얼 엔진의 메모리 관리


📌 3-1. 언리얼 컨테이너 라이브러리 1 - Array와 Set

1. TArray, TSet 컨테이너 라이브러리 내부 구조와 활용 방법
2. 디버그 빌드를 사용해 메모리 정보를 확인하는 방법의 학습
3. 두 컨테이너 라이브러리가 가진 특징의 이해

 

* 언리얼 컨테이너 라이브러리 (UCL)
- TArray: 오브젝트를 순서대로 담아 효율적으로 관리하는 용도로 사용된다. / 동적 가변 배열, vector와 비슷
빈틈없는 메모리, 가장 높은 접근성능, 가장 높은 순회성능
const int32 ArrayNum = 10;
TArray<int32> Int32Array;
for (int32 ix = 1; ix < ArrayNum; ++ix)
{
Int32Array.Emplace(ix);
}
Int32Array.RemoveAll(
[](int32 Val)
{
return Val % 2 == 0;
}
);
Int32Array += {2, 4, 6, 8, 10};
TArray<int32> Int32ArrayCompare;
int32 CArray[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 10 };
Int32ArrayCompare.AddUninitialized(ArrayNum);
FMemory::Memcpy(Int32ArrayCompare.GetData(), CArray, sizeof(int32) * ArrayNum);
ensure(Int32Array == Int32ArrayCompare);
- TSet: 중복되지 않는 요소로 구성된 집합을 만드는 용도로 사용된다. / 해시테이블(중복x), 동적 가변 배열, unordered_set과 비슷
빠른 중복 감지, 순서 상관 없는 데이터 집합
TSet<int32> Int32Set;
for (int32 ix = 1; ix <= ArrayNum; ++ix)
{
Int32Set.Emplace(ix);
}


📌 3-2. 언리얼 컨테이너 라이브러리 2 - 구조체와 Map

1. TArray, TSet, TMap 컨테이너 라이브러리 내부 구조와 활용 방법
2. 언리얼 구조체의 선언 방법
3. TSet과 TMap에서 언리얼 구조체를 사용하기 위해 필요한 함수의 선언과 구현 방법

 

* 언리얼 구조체: 단순한 데이터 타입 / 복잡한 인터랙션을 하는 데이터 타입은 UObject, AActor 서브클래스로 구현
데이터 저장/전송에 특화된 가벼운 객체
USTRUCT()
struct FMyStruct
GENERATED_BODY()
UPROPERTY()
FStruct는 일반 C++ 타입으로 정의되기 때문에 멤버 변수를 제외한 구조체 자체는 리플리케이션용으로 간주되지 않는다.
- TSet 또는 TMap의 키값으로 이용하기 위한 구조체의 Hash값 설정 방법
bool operator==(const FStudentData& InOther) const
{
return Order == InOther.Order;
}
friend FORCEINLINE uint32 GetTypeHash(const FStudentData& InStudentData)
{
return GetTypeHash(InStudentData.Order);
}

* 랜덤 함수
- FMath::RandRange(a, b);

* 알고리즘 라이브러리
- "Algo/"
- Accumulate: 합계
int32 Sum = Algo::Accumulate(Int32Array, 0);
- Transform: 이동
TArray<FString> AllStudentsName;
Algo::Transform(StudentsData, AllStudentsName, 
[](const FStudentData& Val)
{
return Val.Name;
}
);

- TMap: 키, 밸류 조합의 레코드를 관리하는 용도로 사용된다. / 튜플 형태의 TSet (해시테이블, 동적 가변 배열), unordered_map과 비슷
TMap<int32, FString> StudentsMap;
Algo::Transform(StudentsData, StudentsMap,
[](const FStudentData& Val)
{
return TPair<int32, FString>(Val.Order, Val.Name);
}
);
const FString TargetName(TEXT("이혜은"));
TArray<int32> AllOrders;
StudentsMapByName.MultiFind(TargetName, AllOrders);


📌 3-3. 언리얼 엔진의 메모리 관리

1. C++ 언어의 고질적인 문제인 포인터 문제의 이해
2. 이를 해결하기 위한 가비지 콜렉션의 동작 원리의 이해와 설정 방법
3. 다양한 상황에서 언리얼 오브젝트를 생성하고 메모리에 유지하는 방법에 이해
4. 언리얼 오브젝트 포인터를 선언하는 코딩 규칙의 이해

 

* C++ 메모리 관리 문제점
- 프로그래머가 할당과 해지 짝 맞추기를 해야 한다.
- 메모리 누수 (Leak)
- 허상 포인터 (Dangling pointer): 이미 해제된 무효화된 주소 가리킴
- 와일드 포인터 (Wild pointer): 초기화 안된 값, 엉뚱한 주소 가리킴

* 가비지 컬렉션 시스템
- 마크-스윕 방식 (Mark-Sweep)
1. 저장소에서 최초 검색을 시작하는 루트 오브젝트를 표기한다.
2. 루트 오브젝트가 참조하는 객체를 찾아 마크한다. (Mark)
3. 마크된 객체로부터 다시 참조하는 객체를 찾아 마크하고 이를 계속 반복한다.
4. 이제 저장소에는 마크된 객체와 마크되지 않은 객체의 두 그룹으로 나뉜다.
5. 가비지 컬렉터가 저장소에서 마크되지 않은 객체(가비지)들의 메모리를 회수한다. (Sweep)
- 언리얼 엔진
GCCycle 기본값 60초 (프로젝트 설정)
GUObjectArray: 모든 언리얼 오브젝트의 정보를 저장하는 전역 변수
Garbage 플래그: 다른 언리얼 오브젝트로부터의 참조가 없어서 회수 예정인 오브젝트 (시스템 자동 설정)
RootSet 플래그: 다른 언리얼 오브젝트로부터 참조가 없어도 회수하지 않는 특별한 오브젝트 (AddToRoot 함수 호출)

* 언리얼 메모리 관리 장점
- 메모리 누수: 가비지 컬렉터 / C++: 스마트 포인터 라이브러리 활용
- 댕글링 포인터: ::IsValid() / C++: 스마트 포인터 라이브러리 활용
- 와일드 포인터: UPROPERTY / C++: 직접 초기화 or 스마트 포인터 라이브러리 활용

* 회수되지 않는 언리얼 오브젝트
- UPROPERTY
- AddReferencedObject
- AddToRoot

* 가비지 컬렉션 테스트 환경 제작 실습
- FGCObject: 일반 C++가 상속받도록 설계