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 에서 열람할 수 있다.