포스트

TIL 2026-04-10

TIL 2026-04-10

2026-04-10 언리얼 GC / UBT / UHT / CDO / 리플렉션

목차


new는 함수인가?

핵심 답변

new함수가 아니라 C++ 연산자(operator) 입니다.

단, 내부적으로 메모리 할당을 위해 operator new라는 함수를 호출합니다.

new 표현식의 실행 단계

1
Player* p = new Player();

위 한 줄이 내부적으로 하는 일:

1
2
3
4
5
6
7
8
// 1단계: operator new 호출 — 메모리만 할당
void* mem = operator new(sizeof(Player));

// 2단계: 생성자 호출 — 할당된 메모리 위에서 초기화
new(mem) Player();  // placement new로 생성자만 호출

// 3단계: 타입 포인터로 반환
Player* p = static_cast<Player*>(mem);

new는 이 3단계를 하나로 묶은 연산자. malloc은 1단계만 하는 함수.

operator new vs new 표현식

 new 표현식operator new
분류연산자함수
역할메모리 할당 + 생성자 호출메모리 할당만
오버로딩불가가능

복기

  • new연산자, mallocC 라이브러리 함수
  • new 내부에서 operator new(함수)를 호출해 메모리를 확보하고, 이후 생성자를 호출
  • operator new오버로딩 가능 — 클래스별 커스텀 메모리 관리 가능

UE_LOG vs AddOnScreenDebugMessage

항목UE_LOGAddOnScreenDebugMessage
출력 위치Output Log (콘솔/로그 파일)게임 화면 좌상단
영구성로그 파일에 저장됨일정 시간 후 사라짐
빌드Shipping 빌드에서 제거 가능디버그/개발 빌드 전용
주 용도디버깅, 로깅, 에러 추적빠른 시각 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// UE_LOG
UE_LOG(LogTemp, Warning, TEXT("Health: %d"), ActorA->Health);
UE_LOG(LogTemp, Error,   TEXT("Null pointer!"));

// AddOnScreenDebugMessage
if (GEngine)
{
    GEngine->AddOnScreenDebugMessage(
        -1,       // key: -1이면 매번 새 줄
        5.0f,     // 표시 시간(초)
        FColor::Red,
        FString::Printf(TEXT("Health: %d"), Health)
    );
}

로그 레벨: DisplayWarningErrorFatal(크래시)


언리얼 GC 작동방식

핵심 답변

언리얼 엔진은 Tracing GC (Mark & Sweep) 방식을 사용합니다.
UObject를 상속한 객체만 GC 대상이며, UPROPERTY()로 선언된 포인터만 GC가 추적합니다.

GC 동작 단계

단계설명
1. Mark (표시)Root Set에서 시작해 UPROPERTY로 연결된 UObject를 reference graph로 탐색, 도달 가능한 객체에 “살아있음” 표시
2. Sweep (정리)표시되지 않은 UObject를 메모리에서 해제

GC 실행 주기 및 UE5 변경점

  • 자동 실행: 기본 30~60초 간격 (가용 메모리에 따라 더 자주 실행될 수 있음)
  • 기존 GC 한계: 모든 UObject를 한 번에 순회 → 오브젝트가 많으면 프레임 히치 발생
  • UE5 Incremental GC: 여러 프레임에 걸쳐 점진적으로 GC 수행 → 히치 감소
  • ForceGarbageCollection() 직접 호출 가능 (프레임 히치 주의)

UPROPERTY() 역할

1
2
3
4
5
6
7
8
9
10
UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
public:
    UPROPERTY()  // GC가 이 포인터를 추적 → 수거 안 됨
    UMyComponent* SafeComp;

    UMyComponent* DangerousComp;  // UPROPERTY 없음 → GC가 수거할 수 있음!
};

UPROPERTY() 없는 포인터는 GC가 참조를 모름 → 객체 수거 후 댕글링 포인터 발생

복기

  • GC 대상: UObject 상속 클래스만
  • UPROPERTY(): GC 추적의 핵심 — 없으면 수거됨
  • IsValid(): GC 수거 여부 런타임 확인
  • AddToRoot() / RemoveFromRoot(): 강제로 GC 수거 막기/풀기

UBT — Unreal Build Tool

핵심 역할

UBT는 빌드 프로세스의 첫 번째 관문입니다.
가장 먼저 실행되어 전체 프로젝트 구조를 파악하고 플랫폼별 환경을 설정합니다.

빌드 흐름

1
UBT 실행 → 환경 설정 → UHT 실행 → 컴파일 → 링크

주요 기능

  • 플러그인 및 모듈 검색 — Plugins, Modules 디렉터리 탐색
  • .Target.cs 파일 분석 — 빌드 대상(Editor/Game/Server 등) 결정
  • .Build.cs 파일 처리 — 모듈별 의존성 및 컴파일 설정
  • 플랫폼 호환성 확인 — Win64, Mac, Linux 등
  • 개발 환경 자동 구성 — VS/Xcode 프로젝트 파일 생성

Rules Assembly 동작 방식

  • .Build.cs.Target.csC# 파일이며, UBT가 이를 Rules Assembly(공유 라이브러리)로 컴파일한 뒤 실행
  • 빌드 규칙이 단순 텍스트가 아닌 실행 가능한 C# 코드 → 조건분기, 루프 등 로직 작성 가능
1
2
3
4
5
6
7
8
9
10
11
public class MyGame : ModuleRules
{
    public MyGame(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
        PublicDependencyModuleNames.AddRange(new string[]
        {
            "Core", "CoreUObject", "Engine", "InputCore"
        });
    }
}

복기

  • UBT는 빌드 도구, UHT는 헤더 분석 도구 — 역할이 다름
  • .Build.cs/.Target.cs는 C# 코드 → Rules Assembly로 컴파일되어 실행
  • .Build.cs 없으면 모듈이 인식 안 됨

UHT — Unreal Header Tool

핵심 역할

UHT는 컴파일 전 마법사입니다.
.h 파일에서 UCLASS, UPROPERTY, UFUNCTION, USTRUCT, UENUM 매크로를 읽어 메타데이터(리플렉션 정보) 를 생성합니다.

UHT가 생성하는 파일

파일설명
MyObject.generated.hhelper 함수 및 thunk 함수 포함 (헤더별 1개)
MyObject.gen.cpp리플렉션 데이터 실체 구현 (헤더별 1개)
[Module].generated.inl모듈 단위 리플렉션 데이터 통합 (모듈별 1개)

생성된 리플렉션 데이터는 바이너리와 함께 컴파일되므로 구식화(stale) 되는 일이 없음

1
2
3
4
5
6
7
8
9
10
11
UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()  // UHT가 generated.h를 삽입하는 자리
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 Health = 100;

    UFUNCTION(BlueprintCallable)
    void TakeDamage(int32 Amount);
};

복기

  • UHT는 컴파일 전에 실행, generated.h를 만들어 컴파일러에 제공
  • GENERATED_BODY() 없으면 컴파일 에러
  • UPROPERTY/UFUNCTION이 없으면 에디터·블루프린트에서 안 보임

리플렉션 시스템

리플렉션이란?

런타임에 클래스/변수/함수의 타입 정보에 접근하는 기능입니다.
C++ 표준에는 리플렉션이 없기 때문에, UE는 UHT + 매크로로 자체 구현했습니다.

빌드 흐름

1
2
3
4
5
UBT 실행
  └→ UHT 실행: UCLASS/UPROPERTY/UFUNCTION 매크로 분석
         └→ MyClass.generated.h 생성 (리플렉션 데이터 포함)
               └→ 컴파일러가 generated.h 포함해 컴파일
                     └→ 런타임: 리플렉션 시스템 활성화

리플렉션 타입 계층 (UE 내부 표현)

C++ 원본UE 타입역할
class (UObject 상속)UClass클래스 메타데이터
structUScriptStruct구조체 메타데이터
functionUFunction함수 메타데이터
enumUEnum열거형 메타데이터
member variableFProperty멤버 변수 메타데이터 (UE5에서 UProperty → FProperty 리네임)

UPROPERTY() 하나가 담당하는 것

1
2
3
4
5
6
7
8
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 Health = 100;
// ↑ 이 한 줄이:
// 1. 에디터 디테일 패널 노출
// 2. 블루프린트에서 읽기/쓰기 가능
// 3. GC 추적 대상 등록
// 4. 직렬화(저장/로드) 자동 처리
// 5. CDO → 인스턴스 복사 시 값 전달

C++ RTTI vs UE 리플렉션

 C++ RTTIUE 리플렉션
에디터 연동불가가능
GC 연동불가가능
UE에서 사용금지 (비활성화)권장

CDO — Class Default Object

CDO란?

UObject 클래스마다 하나씩 존재하는 기본값 보관소입니다.
붕어빵 틀(CDO) → 붕어빵들(Instance Objects)

생성 시점

1
2
UBT → UHT → 컴파일 → 엔진 시작 시 CDO 생성 (생성자 딱 한 번 호출)
Module Loading → CDO Created → Instances Copy from CDO

CDO 접근 방법

1
2
3
4
5
6
7
8
// 공식 방법 — StaticClass()를 통한 접근
ATestMyActor* CDO = Cast<ATestMyActor>(ATestMyActor::StaticClass()->GetDefaultObject());

// 헬퍼 함수 — 수정 가능
ATestMyActor* MutableCDO = GetMutableDefault<ATestMyActor>();

// 헬퍼 함수 — 읽기 전용
const ATestMyActor* ReadOnlyCDO = GetDefault<ATestMyActor>();

C++ 클래스 vs 블루프린트 클래스의 CDO 차이

 C++ 클래스블루프린트 클래스
CDO 기본값 출처C++ 생성자.uasset 파일
에디터 수정컴파일 시점에 고정블루프린트 에디터에서 저장

CDO 최적화 원리

원리설명
메모리 절약공통 기본값을 CDO 하나에만 저장
빠른 초기화생성자 호출 없이 메모리 복사로 즉시 초기화
델타 직렬화CDO와 차이점만 기록·전송 → 파일 용량·네트워크 효율 극대화

CDO 코드 실습 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void AMyGameModeBase::BeginPlay()
{
    Super::BeginPlay();

    ATestMyActor* ActorA = GetWorld()->SpawnActor<ATestMyActor>(); // CDO 기본값 100 복사
    UE_LOG(LogTemp, Warning, TEXT("ActorA Init Health : %d"), ActorA->Health);  // 100

    ActorA->Health = 50;  // 인스턴스만 변경, CDO 무관
    UE_LOG(LogTemp, Warning, TEXT("ActorA Modi : %d"), ActorA->Health);  // 50

    ATestMyActor* MyActorCDO = GetMutableDefault<ATestMyActor>();
    UE_LOG(LogTemp, Warning, TEXT("CDO Init Health : %d"), MyActorCDO->Health);  // 100

    MyActorCDO->Health = 200;  // CDO 수정
    UE_LOG(LogTemp, Warning, TEXT("CDO Modfi Health : %d"), MyActorCDO->Health);  // 200

    ATestMyActor* ActorB = GetWorld()->SpawnActor<ATestMyActor>(); // CDO가 200이므로 200 복사
    UE_LOG(LogTemp, Warning, TEXT("ActorB Init Health : %d"), ActorB->Health);  // 200

    UE_LOG(LogTemp, Warning, TEXT("ActorA Current HP : %d"), ActorA->Health);  // 50 (변화 없음)
}
로그출력값이유
ActorA Init Health100CDO 기본값(100)에서 복사
ActorA Modi50인스턴스 값만 변경
CDO Init Health100CDO 원본값
CDO Modfi Health200CDO 자체를 200으로 수정
ActorB Init Health200CDO가 200인 후 생성 → 200 복사
ActorA Current HP50이미 생성된 인스턴스는 CDO 변경 영향 없음

주의: 같은 PIE 세션 내에서 CDO 수정은 이후 SpawnActor에 영향을 줌.
PIE 재실행 시 SpawnActor는 생성자 기본값을 사용하지만, GetMutableDefault가 반환하는 CDO 객체에는 이전 수정값이 남아있을 수 있음 → 상태 불일치 발생 가능


언리얼 엔진 C++ 코드 컨벤션

명명 규칙

접두사의미예시
T템플릿 클래스TArray, TMap
UUObject 상속UStaticMeshComponent
AAActor 상속ACharacter, AGameMode
SSWidget 상속 (Slate)SButton
I추상 인터페이스IInterface
E열거형ECollisionChannel
F그 외 대부분FVector, FString
bbool 변수bIsDead, bHasFadedIn

이식성 타입

C++ 기본 타입UE 타입크기 보장
intint32항상 32비트
unsigned intuint32항상 32비트
unsigned charuint8항상 8비트
long longint64항상 64비트
std::stringFStringUE 문자열

언어 기능 제한

항목대신 사용
RTTI (dynamic_cast, typeid)UE 자체 리플렉션 시스템
C++ 예외 (try/catch)check() / ensure()
std::stringFString
new (UObject)NewObject<T>()
goto사용 금지
1
2
3
4
5
6
7
// UObject 생성
UMyComponent* Comp = NewObject<UMyComponent>(this);  // 올바름
// UMyComponent* Comp = new UMyComponent();          // 금지

// 오류 처리
check(Pointer != nullptr);   // 치명적, 크래시
ensure(Value > 0);           // 비치명적, 로그 후 계속
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.