Assert 란?
Assert는 단어 그 자체로 단언하다, 옹호하다 라는 의미이다.
프로그래밍에서의 Assert는 런타임에서 주어진 코드가 가정하는 상황을 검증하는 방법이다.
포인터의 NULL 여부를 검증하는 간단한 것에서부터, 특정 함수에 재진입했는지와 같은 복잡한 검증도 가능하다.
Run Time Assert Macro
언리얼 엔진 4에는 이러한 유형의 검증을 하기 위한 Run TIme Assert Macro 매크로 시리즈가 제공된다. 매크로로 만든 이유는 특정 빌드 환경설정에서 컴파일시 제외시킬 수 있도록 하기 위함이고, 컴파일시 제외를 고려한 이유는 특정 플랫폼에서 퍼포먼스 상의 이슈가 존재하거나 최종 빌드에서는 필요하지 않기 때문이다.
런타임 어서트 매크로는
- 실행 중지
- 디버그 빌드에서 실행 중지
- 실행 중지하지 않고 오류 보고
이렇게 총 3가지 카테고리에 들어간다.
첫째 셋쨰 유형은 DO_CHECK define 에 따라 컴파일된다.
둘째 유형은 DO_GUARD_SLOW define을 사용해 컴파일된다.
그 디파인중 어느 하나가 0으로 설정되면, 매크로는 컴파일에서 제외되어 실행에 전혀 영향을 주지 않는다.
check(expressions)
//check macro example
check(Mesh != nullptr);
가장 간단한 형태의 CEHCK MACRO이다.
이 매크로는 표현식을 실행한 뒤, Assert 결과가 false 일 경우 실행을 중지시킨다. 표면식은 매크로가 빌드에서 컴파일되는 경우에만 실행된다.(DO_CHECK=1)
checkf(expressions, …)
//checkf macro example
checkf(WasDestroyed, TEXT( "Failed to destroy Actor %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel());
checkf( TCString<ANSICHAR>::Strlen( Key ) >= KEYLENGTH( AES_KEYBITS ), TEXT( "AES_KEY needs to be at least %d characters" ), KEYLENGTH( AES_KEYBITS ) );
checkf() 매크로는 표현식이 true가 아니면 디버깅에 도움이 되는 추가 정보를 출력하는 것이 가능합니다. 컴파일 면에 있어서 check()와 똑같이 DO_CHECK=1 인 상태에서 사용가능하다.
verify(expressions)
//verify macro example
verify((Mesh = GetRenderMesh()) != nullptr);
DO_CHECK 가 켜져있으면, 이 매크로는 check()와 똑같은 역할을 한다. 하지만, 이 표면식은 DO_CHECK가 꺼져있어도 실행된다. 변수의 할당이 가정한 대로 되었는지 검증하는데 사용할 수 있다.
verifyf(expressions, …)
verifyf(Module_libeay32, TEXT("Failed to load DLL %s"), *DLLToLoad);
verify() 매크로도 항상 표현식을 실행하듯, verifyf()도 마찬가지다. checkf() 처럼 실행을 중지시키면서, 추가적인 디버그 메시지를 남긴다.
checkCode(expressions)
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() ); } );
이 매크로는 일반적인 check() 보다 약간 더 복잡하다. 조건 체크를 위한 체크 부분과, 조건이 맞을 경우 실행할 코드 부분이 나누어져있다.
엔진에서 자주 사용하지는 않지만, 필요하면 언제든 쓸 수 있다. 표준 check() 매크로처럼 이 매크로도 DO_CHECK=0 일 경우 컴파일에서 제외된다. 그때문에 필수적인 부가 효과가 있는 표현식은 사용하면 안된다. 왜나하면 DO_CHECK가 꺼지면 코드가 제거되기 때문이다.
checkNoEntry()
switch (MyEnum)
{
case MyEnumValue:
break;
default:
checkNoEntry();
break;
}
이 매크로는 표현식을 받지 않으며, 절대 실행될 일이 없는 코드 경로를 표시하는 데 사용된다.
checkNoReentry()
void NoReentry()
{
checkNoReentry();
}
이 매크로는 호출이 주어진 함수에 재진입하는 것을 방지하기 위해 사용된다. 한 번 호출이 완료되기 전까지 다시 호출해서는 안될 함수에 사용하면 된다.
checkNoRecursion()
int32 Recurse(int32 A, int32 B)
{
checkNoRecursion();
return Recurse(A - 1, B - 1);
}
checkNoReentry() 와 같은 검사를 하나, 재귀호출을 막는다는 의도를 더욱 명확히 알 수 있는 매크로이다.
unimplemented()
class FNoImpl
{
virtual void DoStuff()
{
// You must override this
unimplemented();
}
};
DO_CHECK 매크로의 첫 클래스 내 마지막 매크로는 함수에 구현이 없어서 특정 클래스에서 호출하면 안되거나 덮어써야 하는 함수를 표시하는 데 사용된다.
어서트 매크로의 둘째 클래스는 DO_GUARD_SLOW 가 켜졌을 때만 실행된다. DO_GUARD_SLOW 는 보통 디버그 빌드에만 켜기는 하나, 프로젝트에 직접 바꿀 수 있기는 하나. 느리면서 규칙을 지나치게 따지는 검사일 것으로 기대되어, Development또는 Shipping 빌드에서는 필요치 않다. 이 매크로들의 기능은 느리지 않은 버전과 똑같다.매크로는 checkSlow(), checkfSlow(), verifySlow() 이다.
checkSlow(List->HasCycle());
checkfSlow(Class->IsA(AActor::StaticClass()), TEXT("Class (%s) is wrong type"), Class->GetName());
verifySlow(LastValue == Current);
런타임 어서트의 마지막 클래스는 실행은 중지시키지는 않지만, 문제 추적에 도움이 되는 콜스택을 만드는 데 사용된다. 이 매크로의 표현식은 항상 실행되며, 조건식 안에 넣을 수 있다. 이 매크로는 DO_CHECK=1 define으로 켠다.
ensure(expressions)
if (ensure( InObject != NULL ))
{
InObject->Modify();
}
표현식을 검증하여 실패하면 그 지점까지 이르는 콜스택을 생성한다.
ensureMsg(expressions, Msg)
ensureMsg(Node != nullptr, TEXT("Node is invalid"));
표현식을 검증, 리포트에 메시지를 추가시킨 콜스택을 생성한다.
ensureMsgf(expressions, Msg, …)
if (ensureMsgf(!bModal, TEXT("Could not create dialog because modal is set to (%d)"), int32(bModal)))
{
...
}
표현식을 검증, 생성된 리포트에 대한 콜스택과 짝지은 상세 정보를 포함시킵니다. checkf() 나 verifyf() 처럼, 문제 추적에 도움이 되는 컨텍스트 정보를 포함시킬 수 있습니다.
모든 AssertionMacro들은 /UE4/Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h 에서 열람할 수 있다.
글을 다 보지는 않았습니다만… 틀린 부분이 있는것 같습니다.
verify어설션은 DO_CHECK가 꺼져있을때는 실행되지 않는다고 언리얼 도큐먼트에 나와 있습니다!
안녕하세요 Fachidiot님
verify Assertion은 DO_CHECK가 꺼져있을 경우 expression은 실행하되, verify 자체는 무시가 되는 것으로 알고 있습니다.
테스트를 해보려고 했는데 SHIPPING 에서는 로깅을 하려면 엔진 빌드를 한 번 다시 해야되는데 나중에 시간나면 해보겠습니다.
일단 테스트 코드를 대충 짜봤는데 아래와 같습니다.
void ATTGameModeBase::StartPlay()
{
Super::StartPlay();
check(TESTER("CHECK!"));
verify(TESTER("VERIFY!"));
}
bool ATTGameModeBase::TESTER(FString Str)
{
UE_LOG(LogTemp, Warning, TEXT("%s"),*Str);
return false;
}
SHIPPING으로 할 경우 DO_CHECK에 대한 definition이 0으로 될겁니다. 이때 verify 자체에 대한 결과는 무시가 될 것입니다만, 내부에서 호출한 함수인 TESTER 함수는 verify로 되어있다고 해도 호출이 될 것 같습니다.
check로 넣은 TESTER는 아예 expression 자체가 불리지 않고 무시될 것이구요.
언리얼 영어 문서와 한국어 문서가 약간 설명이 다르긴 한 것 같은데 일단 영어 문서를 더 신뢰해서 verify 카테고리 쪽 문장을 가져와봤습니다.
https://docs.unrealengine.com/en-US/ProgrammingAndScripting/ProgrammingWithCPP/Assertions/index.html
여기서 Verify macros evaluate their expressions even in builds where Check macros are disabled. 라고 되어있는데 실제 Assertion 정의부를 가보니까 #ifdef DO_CHECK 로 check와 verify 둘다 묶여있어서 되는건지 안되는건지는 나중에 엔진 빌드좀 다시 해보고 확실히 말씀드리겠습니다.
좋은 질문 남겨주셔서 감사합니다.