이번에 실습을 진행하면서 TArray의 checkf 함수 내 에서 중단점이 발생하였습니다.
프로젝트의 규모가 커질 수록 이런 중단점이 어디에서 발생하는지를 모른다면
디버깅에 시간이 몇배는 더 많이 걸릴 것이라는 생각이 드는 순간이였고..
정말 식은땀이 났습니다.
아.. 전에 튜터님께서 Assert의 중요성에 대해서 이야기해주신적이 있었는데
Assert의 중요성이 와닿는 순간이였습니다.
Assert란 주어진 코드 조각이 가정하는 상황을 검증하는 도구입니다.
포인터의 NULL여부를 검증하는 간단한 것부터
특정 함수에 재진입했는지와 같은 복잡한 검증도 가능합니다.
런타임 어써트 매크로는 크게 3가지로 나뉩니다.
1. 실행 중지
2. 디버그 빌드에서 실행 중지
3. 실행 중지하지 않고 오류 보고
이 중 1,3번째 유형은 DO_CHECK 디파인에 따라 컴파일되고
2번째 유형은 DO_GUARD_SLOW 디파인을 사용하여 컴파일 됩니다.
그 디파인 중 어느 하나가 0으로 설정되면
매크로는 비활성화되어 실행에 영향을 끼치지 않습니다.
또, assert의 주요 특징은 출시코드에서는 존재하지 않는다는 것입니다.
assert 매크로의 사용법을 알아보겠습니다.
1. Check계열
- check 시리즈:
- 전달받은 조건이 false가 되면 프로그램 실행을 중지(Assert 발생).
- 기본적으로 디버그(Debug), 개발(Development), 테스트(Test), 출시 에디터(Shipping Editor) 빌드에서만 동작.
- 실제 최종 출시(Shipping) 빌드에서는 실행되지 않음(기본 설정).
- Slow로 끝나는 매크로(checkSlow, checkfSlow):
- 보통 check 매크로와 같은 용도로 쓰이지만, 디버그 빌드에서만 동작한다는 점이 다름.
- “성능 비용이 큰 검사이므로, 디버그 상황에서만 활성화하자”는 의도가 담겨 있음.
- USE_CHECKS_IN_SHIPPING:
- 기본값: 0 (즉, check 계열 매크로가 출시 빌드에서 비활성화됨)
- 1로 설정할 경우, 출시 빌드에서도 check 계열 매크로가 활성화됨
- 프로젝트 최종 출시 시에는 보통 0으로 두는 것을 권장(성능 문제, 불필요한 크래시 방지 등
1 - 2 . check 예시
(1) check
void UMyClass::InitializeObject(UObject* Object)
{
// Object가 null이면 즉시 프로그램 중단(Assert)
check(Object != nullptr);
// Object가 유효하므로 안전하게 함수 호출 가능
Object->DoSomething();
}
(2) checkSlow
void UMyClass::ProcessWorld(UWorld* World)
{
// World가 게임 월드인지 확인 (디버그 빌드에서만 검사)
checkSlow(World->IsGameWorld());
// 디버그 빌드가 아닐 때는 이 코드는 무시되고 실행 비용 절감
// 실제 실행 로직
World->SpawnActor<AActor>();
}
(3) checkf
void UMyClass::SetHealth(int32 NewHealth)
{
// NewHealth가 0 이상이어야 한다고 가정
checkf(NewHealth >= 0, TEXT("Health cannot be negative! Current: %d"), NewHealth);
// 정상 범위라면 값 설정
CurrentHealth = NewHealth;
}
(4) checkfSlow
void UMyClass::CheckIndexSlow(int32 Index)
{
// 디버그 빌드에서만 검사 + 상세 메시지 로그
checkfSlow(Index >= 0, TEXT("Index must be non-negative! Given: %d"), Index);
// 실제 로직...
}
(5) checkCode
checkCode(if (Object->HasAnyFlags(RF_PendingKill))
{
UE_LOG(LogUObjectGlobals, Fatal,
TEXT("Object %s is part of root set though has been marked RF_PendingKill!"),
*Object->GetFullName());
});
(6) checkNoEntry
void UMyClass::ProcessValue(int32 Value)
{
switch (Value)
{
case 0:
UE_LOG(LogTemp, Log, TEXT("Value is zero"));
break;
case 1:
UE_LOG(LogTemp, Log, TEXT("Value is one"));
break;
default:
// 절대 들어오면 안 되는 경우라면 checkNoEntry() 사용
checkNoEntry();
break;
}
}
(7) checkNoReentry
void UMyClass::NonReentrantFunction()
{
// 같은 스레드/루틴에서 이 함수에 재진입하면 중단.
checkNoReentry();
UE_LOG(LogTemp, Log, TEXT("NonReentrantFunction is running..."));
// 어떤 긴 작업 수행
FPlatformProcess::Sleep(2.0f);
// 함수 종료 시점에 매크로가 내부적으로 다시 '해제'해 준다고 보면 됩니다.
}
(8) checkNoRecursion
void UMyClass::NoRecursiveCall(int32 Value)
{
checkNoRecursion();
// 이 함수가 재귀적으로 다시 호출되면 Assert 발생
UE_LOG(LogTemp, Log, TEXT("NoRecursiveCall with Value=%d"), Value);
if (Value > 0)
{
// 아래 코드를 실행하면 바로 재귀 발생 -> 중단
// NoRecursiveCall(Value - 1);
}
}
(9) unimplemented
// 부모 클래스
class UMyBaseClass
{
public:
virtual void DoSomething()
{
// 자식 클래스가 반드시 재정의해야 하는 함수지만, 자식에서 구현이 되어있지 않다면
unimplemented();
// 호출 시 check(false)처럼 프로그램 중단
}
};
// 자식 클래스
class UMyChildClass : public UMyBaseClass
{
public:
virtual void DoSomething() override
{
// 정상적인 구현
UE_LOG(LogTemp, Log, TEXT("UMyChildClass::DoSomething() called!"));
}
};
1 - 3 . Check 정리
- check 계열은 “특정 조건이 반드시 참이어야 한다”는 의도를 코드로 명시하는 디버그 도구입니다.
- 기본적으로 디버그/개발 빌드에서만 동작하고, 출시 빌드에서는 빠집니다.
- Slow가 붙은 매크로는 성능 부담이 더 크므로 보통 디버그 빌드에서만 활성화합니다.
- 출시 빌드에서 check가 동작하길 원한다면 USE_CHECKS_IN_SHIPPING=1로 설정할 수 있지만, 권장되지는 않습니다.
- “이 코드 경로는 절대 실행되지 않아야 한다”거나 “여기 호출되면 에러” 같은 곳에 checkNoEntry, unimplemented 등을 사용해 두면 디버깅 시 유용합니다.
2 . Verify
(1)
- USE_CHECKS_IN_SHIPPING = 1로 설정 시, Shipping 빌드에서도 디버그 빌드처럼 Assert 발생.
- 일반적으로는 출시 빌드에서 Assert를 일으키지 않도록 설정해 성능 및 사용자 경험을 보장.
- verify 계열의 특징: 표현식이 항상 평가된다는 점. (반면 check 계열은 빌드에 따라 표현식 자체가 제거될 수도 있음.)
3. Ensure
- Ensure 계열은 “치명적이지 않은 오류” 시점을 Crash Reporter에 알리고, 게임(프로그램)은 중단 없이 계속 진행하도록 합니다.
- 디버그/개발 빌드에서는 여러 번 false가 발생해도, 기본 ensure는 처음 한 번만 보고하고, 이후에는 리포트를 생략(플러딩 방지).
- 단, ensureAlways 계열은 매번 보고.
- **출시 빌드(Shipping)**에선 기본적으로 Crash Reporter로 보고하지 않지만, 표현식 자체는 평가가 이루어져서 복구 로직 등은 적용 가능.
만일 어써트가 없었다면..어떻게 디버깅을 할지 생각만해도 끔직하다
스택을 하나하나 중단점을 찍어가며 확인해야했을것이다.
출처 : https://dev.epicgames.com/documentation/ko-kr/unreal-engine/asserts-in-unreal-engine
'Unreal > Unreal 공부 내용' 카테고리의 다른 글
GetOverlappingActors(TArray<AActor*> Array) , IsA (0) | 2025.02.09 |
---|---|
TSubClassOf , UClass* , StaticClass, 클래스,인스턴스,메타데이터(미완성) (0) | 2025.02.05 |
Timer(SetTimer...) (0) | 2025.01.24 |
구면선형보간법(SLERP) (0) | 2025.01.22 |
선형 보간 (0) | 2025.01.22 |