다음을 통해 공유


C/C++ 어설션

어설션 문은 프로그램의 한 지점에서 true로 예상되는 조건을 지정합니다. 해당 조건이 true가 아니면 어설션이 실패하고 프로그램 실행이 중단되고 어설션 실패 대화 상자 가 나타납니다.

Visual Studio는 다음 구문을 기반으로 하는 C++ 어설션 문을 지원합니다.

  • MFC 프로그램에 대한 MFC 어설션입니다.

  • ATL을 사용하는 프로그램에 쓰이는 ATLASSERT입니다.

  • C 런타임 라이브러리를 사용하는 프로그램에 대한 CRT 어설션입니다.

  • 다른 C/C++ 프로그램에 대한 ANSI 어설션 함수 입니다.

    어설션을 사용하여 논리 오류를 탐지하고, 작업 결과를 확인하며, 처리해야 하는 오류 조건을 검사할 수 있습니다.

이 항목의 내용

어설션 작동 방식

디버그 및 릴리스 빌드의 어설션

어설션 사용의 부작용

CRT 어설션

MFC 어설션

어설션 작동 방식

MFC 또는 C 런타임 라이브러리 어설션으로 인해 디버거가 중지되면 원본을 사용할 수 있는 경우 디버거는 어설션이 발생한 원본 파일의 지점으로 이동합니다. 어설션 메시지는 출력 창어설션 실패 대화 상자에 모두 나타납니다. 나중에 참조할 수 있도록 저장하려는 경우 출력 창에서 텍스트 창으로 어설션 메시지를 복사할 수 있습니다. 출력 창에도 다른 오류 메시지가 포함될 수 있습니다. 이러한 메시지는 어설션 실패의 원인에 대한 단서를 제공하기 때문에 신중하게 검토합니다.

어설션을 사용하여 개발 중에 오류를 탐지합니다. 일반적으로 각 가정에 대해 하나의 어설션을 사용합니다. 예를 들어 인수가 NULL이 아니라고 가정하는 경우 어설션을 사용하여 해당 가정을 테스트합니다.

이 항목에서

디버그 및 릴리스 빌드의 어설션

어설션 문은 _DEBUG이 정의된 경우에만 컴파일됩니다. 그렇지 않으면 컴파일러는 어설션을 null 문으로 처리합니다. 따라서 어설션 문은 최종 릴리스 프로그램에서 오버헤드나 성능 비용을 전혀 부과하지 않으며, #ifdef 지시문을 사용하지 않아도 되도록 해줍니다.

어설션 사용의 부작용

코드에 어설션을 추가할 때 어설션에 부작용이 없는지 확인합니다. 예를 들어, nM 값을 변경하는 다음의 어설션을 고려해 보십시오.

ASSERT(nM++ > 0); // Don't do this!

프로그램의 릴리스 버전에서는 ASSERT 표현식이 평가되지 않으므로, 디버그 버전과 릴리스 버전에서는 nM 값이 서로 다르게 나타납니다. MFC에서 이 문제를 방지하려면 대신 VERIFY 매크로 ASSERT를 사용할 수 있습니다. VERIFY 는 모든 버전에서 식을 계산하지만 릴리스 버전에서는 결과를 확인하지 않습니다.

함수를 평가하면 예기치 않은 부작용이 발생할 수 있으므로 어설션 문에서 함수 호출을 사용하는 데 특히 주의해야 합니다.

ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe

VERIFY 는 디버그 버전과 릴리스 버전 모두에서 호출 myFnctn 되므로 사용할 수 있습니다. 그러나 VERIFY를 사용하면 릴리스 버전에서 불필요한 함수 호출로 인한 오버헤드를 초래합니다.

이 항목에서

CRT 어설션

헤더 파일은 CRTDBG.H 어설션 검사를 위한 매크로 및 _ASSERT 매크로를 정의_ASSERTE합니다.

Macro 결과
_ASSERT 지정된 식이 FALSE로 평가되면 _ASSERT의 파일 이름과 줄 번호가 표시됩니다.
_ASSERTE 어설션된 식의 문자열 표현을 더한 것과 _ASSERT같습니다.

_ASSERTE는 FALSE로 판명된 어설션된 식을 보고하기 때문에 더 강력합니다. 소스 코드를 참조하지 않고 문제를 식별하기에 충분할 수 있습니다. 그러나 애플리케이션의 디버그 버전에는 을 사용하여 _ASSERTE어설션된 각 식에 대한 문자열 상수가 포함됩니다. 많은 _ASSERTE 매크로를 사용하는 경우 이러한 문자열 식은 상당한 양의 메모리를 차지합니다. 문제가 되는 경우 메모리를 저장하는 데 사용합니다 _ASSERT .

_DEBUG가 정의되었을 때 _ASSERTE 매크로는 다음과 같이 정의됩니다.

#define _ASSERTE(expr) \
    do { \
        if (!(expr) && (1 == _CrtDbgReport( \
            _CRT_ASSERT, __FILE__, __LINE__, #expr))) \
            _CrtDbgBreak(); \
    } while (0)

어설션된 식이 FALSE로 평가되면 _CrtDbgReport 호출되어 어설션 실패를 보고합니다(기본적으로 메시지 대화 상자 사용). 메시지 대화 상자 에서 _CrtDbgReport 선택하면 1을 반환하고 _CrtDbgBreak 디버거DebugBreak를 호출합니다.

모든 어설션을 일시적으로 사용하지 않도록 설정해야 하는 경우 _CtrSetReportMode 사용합니다.

힙 손상 여부 확인

다음 예제에서는 _CrtCheckMemory 사용하여 힙의 손상을 확인합니다.

_ASSERTE(_CrtCheckMemory());

포인터 유효성 검사

다음 예제에서는 _CrtIsValidPointer 사용하여 지정된 메모리 범위가 읽기 또는 쓰기에 유효한지 확인합니다.

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

다음 예제에서는 _CrtIsValidHeapPointer 사용하여 로컬 힙의 메모리를 가리키는 포인터를 확인합니다(C 런타임 라이브러리의 이 인스턴스에서 만들고 관리하는 힙 - DLL은 라이브러리의 자체 인스턴스를 가질 수 있으므로 애플리케이션 힙 외부에서 자체 힙을 가질 수 있습니다). 이 어설션은 null 또는 범위를 벗어난 주소뿐만 아니라 정적 변수, 스택 변수 및 기타 비지역 메모리에 대한 포인터도 포착합니다.

_ASSERTE(_CrtIsValidHeapPointer( myData );

메모리 블록 확인

다음 예제에서는 _CrtIsMemoryBlock 사용하여 메모리 블록이 로컬 힙에 있고 유효한 블록 형식이 있는지 확인합니다.

_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));

이 항목에서

MFC 어설션

MFC는 어설션 검사를 위한 ASSERT 매크로를 정의합니다. MFC ASSERT_VALIDCObject::AssertValid 메서드도 CObject에서 파생된 객체의 내부 상태를 확인하기 위해 정의합니다.

MFC ASSERT 매크로의 인수가 0 또는 false로 평가되면 매크로는 프로그램 실행을 중지하고 사용자에게 경고합니다. 그렇지 않으면 실행이 계속됩니다.

어설션이 실패하면 메시지 대화 상자에 원본 파일의 이름과 어설션의 줄 번호가 표시됩니다. 대화 상자에서 다시 시도를 선택하면 AfxDebugBreak 를 호출하면 실행이 디버거로 중단됩니다. 이 시점에서 호출 스택을 검사하고 다른 디버거 기능을 사용하여 어설션이 실패한 이유를 확인할 수 있습니다. Just-In-Time 디버깅을 사용하도록 설정했고 디버거가 아직 실행되고 있지 않은 경우 대화 상자에서 디버거를 시작할 수 있습니다.

다음 예제에서는 함수의 반환 값을 확인하는 데 사용하는 ASSERT 방법을 보여줍니다.

int x = SomeFunc(y);
ASSERT(x >= 0);   //  Assertion fails if x is negative

ISKindOf 함수와 함께 ASSERT를 사용하여 함수 인수의 형식 검사를 제공할 수 있습니다.

ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );

매크로는 ASSERT 릴리스 버전에서 코드를 생성하지 않습니다. 릴리스 버전에서 식을 평가해야 하는 경우 ASSERT 대신 VERIFY 매크로를 사용합니다.

MFC ASSERT_VALID 및 CObject::AssertValid

CObject::AssertValid 메서드는 개체의 내부 상태에 대한 런타임 검사를 제공합니다. 클래스를 AssertValid에서 파생시킬 때 CObject을(를) 재정의할 필요는 없지만, 이 작업을 통해 클래스를 더 신뢰성 있게 만들 수 있습니다. AssertValid 는 개체의 모든 멤버 변수에 대해 어설션을 수행하여 유효한 값이 포함되어 있는지 확인해야 합니다. 예를 들어 포인터 멤버 변수가 NULL이 아닌지 확인해야 합니다.

다음 예제에서는 함수를 선언하는 방법을 보여줍니다.AssertValid

class CPerson : public CObject
{
protected:
    CString m_strName;
    float   m_salary;
public:
#ifdef _DEBUG
    // Override
    virtual void AssertValid() const;
#endif
    // ...
};

AssertValid를 재정의할 때, 자체 검사를 수행하기 전에 기본 클래스의 AssertValid 버전을 호출하세요. 그런 다음 ASSERT 매크로를 사용하여 다음과 같이 파생 클래스에 고유한 멤버를 확인합니다.

#ifdef _DEBUG
void CPerson::AssertValid() const
{
    // Call inherited AssertValid first.
    CObject::AssertValid();

    // Check CPerson members...
    // Must have a name.
    ASSERT( !m_strName.IsEmpty());
    // Must have an income.
    ASSERT( m_salary > 0 );
}
#endif

멤버 변수 중에 객체를 저장하는 경우 ASSERT_VALID 매크로를 사용하여 내부 유효성을 테스트할 수 있습니다(클래스가 AssertValid를 재정의하는 경우).

예를 들어 멤버 변수 중 하나에 CMyData를 저장하는 클래스를 고려해 보세요. 변수 CObListCPerson 개체 컬렉션을 저장합니다 m_DataList. 약식 선언은 CMyData 다음과 같습니다.

class CMyData : public CObject
{
    // Constructor and other members ...
    protected:
        CObList* m_pDataList;
    // Other declarations ...
    public:
#ifdef _DEBUG
        // Override:
        virtual void AssertValid( ) const;
#endif
    // And so on ...
};

"재정의 사항은 AssertValidCMyData에서 다음과 같이 보입니다:"

#ifdef _DEBUG
void CMyData::AssertValid( ) const
{
    // Call inherited AssertValid.
    CObject::AssertValid( );
    // Check validity of CMyData members.
    ASSERT_VALID( m_pDataList );
    // ...
}
#endif

CMyData 는 메커니즘을 AssertValid 사용하여 해당 데이터 멤버에 저장된 개체의 유효성을 테스트합니다. 재정의된 AssertValidCMyData는 자신의 m_pDataList 멤버 변수에 대한 ASSERT_VALID 매크로를 호출합니다.

클래스 CObListAssertValid을/를 재정의하므로 유효성 테스트가 이 수준에서 중지되지 않습니다. 이 재정의는 목록의 내부 상태에 대한 추가 유효성 테스트를 수행합니다. 따라서 개체에 대한 CMyData 유효성 테스트는 저장된 CObList 목록 개체의 내부 상태에 대한 추가 유효성 테스트로 이어집니다.

더 많은 작업을 통해 목록에 저장된 개체에 CPerson 대한 유효성 테스트를 추가할 수도 있습니다. 당신은 CPersonList 클래스를 CObList로부터 파생시키고 AssertValid를 재정의할 수 있습니다. 재정의에서, 먼저 CObject::AssertValid를 호출하고, 그런 다음 목록에 저장된 각 CPerson 개체에 대해 AssertValid를 호출하면서 목록을 반복합니다. 이 주제의 시작 부분에 표시된 CPerson 클래스는 이미 AssertValid을(를) 재정의합니다.

디버깅을 위해 빌드할 때 강력한 메커니즘입니다. 이후에 릴리스를 위해 빌드하면 메커니즘이 자동으로 꺼집니다.

AssertValid의 제한 사항

트리거된 어설션은 개체가 확실히 잘못되었으며 실행이 중지됨을 나타냅니다. 그러나 어설션이 부족하다는 것은 문제가 발견되지 않았음을 의미하지만, 개체가 양호하다고 보장되지는 않습니다.

이 항목에서

어설션 사용

논리 오류 찾기

프로그램의 논리에 따라 true여야 하는 조건에서 어설션을 설정할 수 있습니다. 논리 오류가 발생하지 않는 한 어설션은 적용되지 않습니다.

예를 들어 컨테이너에서 가스 분자를 시뮬레이션하고 변수가 numMols 총 분자 수를 나타낸다고 가정해 보겠습니다. 이 숫자는 0보다 작을 수 없으므로 다음과 같은 MFC 어설션 문을 포함할 수 있습니다.

ASSERT(numMols >= 0);

또는 다음과 같은 CRT 어설션을 포함할 수 있습니다.

_ASSERT(numMols >= 0);

프로그램이 올바르게 작동하는 경우 이러한 문은 아무 작업도 수행하지 않습니다. 그러나 논리 오류로 인해 numMols 0보다 작은 경우 어설션은 프로그램 실행을 중지하고 어설션 실패 대화 상자를 표시합니다.

이 항목에서

결과 확인

어설션은 간단한 시각적 확인만으로는 결과가 명확하지 않은 작업을 테스트하는 데 유용합니다.

예를 들어, 다음 코드는 mols가 가리키는 연결 리스트의 내용에 따라 변수 iMols를 업데이트합니다.

/* This code assumes that type has overloaded the != operator
 with const char *
It also assumes that H2O is somewhere in that linked list.
Otherwise we'll get an access violation... */
while (mols->type != "H2O")
{
    iMols += mols->num;
    mols = mols->next;
}
ASSERT(iMols<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version

계산되는 iMols 분자의 수는 항상 총 분자 numMols수보다 작거나 같아야 합니다. 루프를 시각적으로 검사해도 반드시 그 조건이 충족된다고 나타나지 않기 때문에, 루프 후에 어설션 문을 사용하여 해당 조건을 테스트합니다.

이 항목에서

처리되지 않은 오류 찾기

어설션을 사용하여 오류가 처리되어야 하는 코드의 지점에서 오류 조건을 테스트할 수 있습니다. 다음 예제에서 그래픽 루틴은 성공에 대한 오류 코드 또는 0을 반환합니다.

myErr = myGraphRoutine(a, b);

/* Code to handle errors and
   reset myErr if successful */

ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version

오류 처리 코드가 제대로 작동하는 경우 어설션에 도달하기 전에 오류를 처리하고 myErr 0으로 다시 설정해야 합니다. 다른 값이 있으면 myErr 어설션이 실패하고 프로그램이 중지되고 어설션 실패 대화 상자 가 나타납니다.

그러나 어설션 문은 오류 처리 코드를 대체하는 것은 아닙니다. 다음 예제에서는 최종 릴리스 코드에서 문제가 발생할 수 있는 어설션 문을 보여 줍니다.

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!

이 코드는 어설션 문을 사용하여 오류 조건을 처리합니다. 따라서 반환된 myGraphRoutine 오류 코드는 최종 릴리스 코드에서 처리되지 않습니다.

이 항목에서