C++ 언어 메모리 관리의 문제점
- C++은 저수준으로 메모리 주소에 직접 접근하는 포인터를 사용해 오브젝트를 관리
- 프로그래머가 직접 할당(new)과 해지(delete) 짝 맞추기가 필요
- 이를 잘 지키지 못하는 경우 다양한 문제가 발생할 수 있음.
- 잘못된 포인터 사용 예시
- 메모리 누수(Leak) : new를 했는데 delete 짝을 맞추지 못함. 힙에 메모리가 그대로 남아있음.
- 허상(Dangling) 포인터 : (다른곳에서) 이미 해제해 무효화된 오브젝트의 주소를 가리키는 포인터
- 와일드(Wild) 포인터 : 값이 초기화되지 않아 엉뚱한 주소를 가리키는 포인터
- 잘못된 포인터 값은 다양한 문제를 일으키며, 한 번의 실수는 프로그램을 종료.
- 게임 규모가 커지고 구조가 복잡해질수록 프로그래머가 실수할 확률은 크게 증가.
- C++ 이후에 나온 언어 Java/C#은 이런 고질적인 문제를 해결하기 위해 포인터를 버리고 대신 가비지 컬렉션 시스템을 도입.
가비지 컬렉션 시스템
- 프로그램에서 더 이상 사용하지 않는 오브젝트를 자동으로 감지해 메모리를 회수하는 시스템
- 동적으로 생성된 모든 오브젝트 정보를 모아둔 저장소를 사용해 사용되지 않는 메모리를 추적
- 마크-스웝(Mark-Sweep) 방식의 가비지 컬렉션
- 저장소에서 최초 검색을 시작하는 루트 오브젝트를 표기한다.
- 루트 오브젝트가 참조하는 객체를 찾아 마크(Mark) 한다.
- 마크한 객체로부터 다시 참조하는 객체를 찾아 마크하고 이를 계속 반복한다.
- 이제 저장소에는 마크된 객체와 마크되지 않은 객체의 두 그룹으로 나뉜다.
- 가비지 컬렉터가 저장소에서 마크되지 않은 객체(가비지)들의 메모리를 회수한다. (Sweep)
언리얼 엔진의 가비지 컬렉션 시스템
- 마크-스윕 방식의 가비지컬렉션 시스템을 자체적으로 구축함
- 지정된 주기마다 몰아서 없애도록 설정되어 있음. (GCCycle. 기본 값 60초)
- 성능 향상을 위해 병렬 처리, 클러스터링과 같은 기능을 탑재.
가비지 컬렉션을 위한 객체 저장소
- 관리되는 모든 언리얼 오브젝트의 정보를 저장하는 전역 변수 : GUObjectArray
- GUObjectArray의 각 요소에는 플래그(FLag)가 설정되어 있음.
- 가비지 컬렉터가 참고하는 주요 플래그
- Garbage 플래그 : 다른 언리얼 오브젝트로부터의 참조가 없어 회수 예정인 프로젝트
- RootSet 플래그 : 다른 언리얼 오브젝트로부터 참조가 없어도 회수하지 않은 특별한 오브젝트
루트셋 플래그의 설정
- AddToRoot 함수를 호출해 루트셋 플래그를 설정하면 최초 탐색 목록으로 설정됨.
- 루트셋으로 설정된 언리얼 오브젝트는 메모리 회수로부터 보호받음.
- RemoveFromRoot 함수를 호출해 루트셋 플래그를 제거할 수 있음.
언리얼 오브젝트를 통한 포인터 문제의 해결
- 메모리 누수 문제
- 언리얼 오브젝트는 가비지 컬렉터를 통해 자동으로 해결
- C++ 오브젝트는 직접 신경써야 함. (스마트 포인터 라이브러리 활용)
- 댕글링 포인터 문제
- 언리얼 오브젝트는 이를 탐지하기 위한 함수를 제공함 ::IsValid()
- C++ 오브젝트는 직접 신경써야 함. (스마트 포인터 라이브러리 활용)
- 와일드 포인터 문제
- 언리얼 오브젝트에 UPROPERTY 속성을 지정하면 자동으로 nullptr로 초기화 해줌
- C++ 오브젝트의 포인터는 직접 nullptr로 초기화 할 것 (또는 스마트 포인터 라이브러리 사용)
참조
이 글은 이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++를 수강하고 작성한 글입니다.
'Unreal' 카테고리의 다른 글
UE 액터 컴포넌트 (1) | 2024.06.08 |
---|---|
UE C++ 빌드 시스템 (0) | 2024.06.01 |
UE C++ 직렬화 (0) | 2024.05.25 |
UE C++ Design - Interface (0) | 2024.04.27 |
UE C++ 오브젝트 (1) | 2024.04.13 |