개요
언리얼에서 특정 메모리에 접근할 때, 혹시 몰라서 IsValid 와 IsValidLowLevel 둘 다 같이 작성했던 경험이 있다. 이번에 두 함수의 차이점에 대해 알아보려고 한다.
IsValid()
체크하는 것들:
- ✅ PendingKill 상태
- ✅ BeginDestroy 호출 여부
- ✅ GC 마킹 상태
- ✅ 실제 사용 가능한 상태인지
따라서
- 접근하려는 메모리가 안전한지 확인하기 위해서는 IsValid 를 사용하자.
IsValid 의 내부를 보면 다음과 같다.
- 해당 UObject 가 Null Ptr 인지 검사한다.
- IsValid 는 Object.h 에 선언된 전역함수이다.
- 해당 UObject가 현재 Garbage 플래그, 즉 GC 대상 오브젝트인지 검사한다.
IsPendingKillPending
함수도CheckObjectValidBasedOnItsFlags
함수를 호출한다.
FORCEINLINE bool IsValid(const UObject *Test)
{
return Test && FInternalUObjectBaseUtilityIsValidFlagsChecker::CheckObjectValidBasedOnItsFlags(Test);
}
FORCEINLINE static bool CheckObjectValidBasedOnItsFlags(const UObject* Test)
{
// Here we don't really check if the flags match but if the end result is the same
checkSlow(GUObjectArray.IndexToObject(Test->InternalIndex)->HasAnyFlags(EInternalObjectFlags::Garbage) == Test->HasAnyFlags(RF_MirroredGarbage));
return !Test->HasAnyFlags(RF_MirroredGarbage);
}
IsValidLowLevel()
체크하는 것들:
- ✅ nullptr인지
- ✅ 메모리상 존재하는지
- ✅ 전역 오브젝트 시스템에 등록되어 있는지
체크하지 않는 것들:
- ❌ PendingKill 상태
- ❌ BeginDestroy 호출 여부
- ❌ GC 마킹 상태
- ❌ 실제 사용 가능한 상태인지
따라서
- 성능이 중요한 저수준 코드에서만 사용 하는 것이 좋다.
IsValidLowLevel 의 내부를 보면 다음과 같다.
bool UObjectBase::IsValidLowLevel() const
{
return IsValidLowLevelForDestruction() && GUObjectArray.IsValid(this);
}
- 전역 오브젝트 배열에서 이 오브젝트가 유효한 상태인지 확인
- 이 때의 IsValid 는 FObjectArray의 멤버함수로 GC 대상인지 확인하지 않는다.
bool UObjectBase::IsValidLowLevelForDestruction() const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (!IsThisNotNull(this, "UObjectBase::IsValidLowLevelForDestruction"))
PRAGMA_ENABLE_DEPRECATION_WARNINGS
{
UE_LOG(LogUObjectBase, Warning, TEXT("NULL object"));
return false;
}
if (!ClassPrivate.GetNoResolve())
{
UE_LOG(LogUObjectBase, Warning, TEXT("Object is not registered"));
return false;
}
return true;
}
- 해당 오브젝트가 Null 인지 확인
- GetNoResolve 함수 안에서는 계속 파고파고 들어가다보면
- 단순히 오브젝트 핸들이 문제가 없으면, 포인터를 반환한다.
- 메모리 관련된 확인은 진행하지 않는다.
GetNoResolve()
↓
NoAccessTrackingGetNoResolve()
↓
NoResolveObjectHandleNoRead()
↓
ReadObjectHandlePointerNoCheck()
IsValid 와 IsValidLowLevel 비교
- 결론 : IsValid 를 사용하는 것이 안전하다.
void ADelegateTestActor::Test()
{
TargetObj = GetWorld()->SpawnActor<ATargetObject>();
TargetObj->Destroy();
UE_LOG(LogTemp, Warning, TEXT("TargetObj -> Destroy()"));
UE_LOG(LogTemp, Warning, TEXT("TargetObj -> IsPendingKill : %d"), TargetObj->IsPendingKillPending());
UE_LOG(LogTemp, Warning, TEXT("TargetObj -> IsValidLowLevel : %d"), TargetObj->IsValidLowLevel());
UE_LOG(LogTemp, Warning, TEXT("TargetObj -> IsValid : %d"), IsValid(TargetObj));
- IsValid 와 IsPendingKill 은 TargetObj 가 GC 대상이기 떄문에 0과 1을 출력한다.
- 하지만 IsValidLowLevel 은 GC가 되기 전이기 때문에 1 을 출력한다.
결과:
LogTemp: Warning: TargetObj -> Destroy()
LogTemp: Warning: TargetObj -> IsPendingKill : 1
LogTemp: Warning: TargetObj -> IsValidLowLevel : 1
LogTemp: Warning: TargetObj -> IsValid : 0
- 언제 GC 될 지 모르기 때문에, IsValidLowLevel 은 1 을 출력할 가능성이 있다.