자료 저장소


ASSERT_VALID

ASSERT_VALID 매크로는 객체의 메모리 영역과 멤버변수에 대한 유효성을 검사하는데 사용되는 매크로 함수 입니다. 이 매크로 함수를 이용하기 위해서는 클래스를 정의 할 때 반드시 CRuntimeClass를 선언 및 구현해 주어야하며, CObject를 상속 받아야 합니다. 이렇게 해주는 이유는 ASSERT_VALID 매크로 함수는 내부적으로 ::AfxAssertValidObject(pOb, THIS_FILE, __LINE__) 함수를 호출하는데, 이 함수 내부에서 CRuntimeClass의 m_nObjectSize 멤버 변수를 이용해 메모리 주소 공간이 옳바른지 체크하는 루틴이 존재하기 때문이며, CObject의 멤버 가상 함수인 AssertValid를 호출하여 객체의 멤버변수에 대한 유효성 검사를 수행하기 때문입니다.

그럼 이제부터, 위의 글로 서술한 내용을 코드를 통해 하나씩 알아보도록 하겠습니다.

우선, ASSERT_VALID 매크로 함수의 원형입니다. 아래에 보시는 봐와 같이 AfxAssertValidObject 함수를 재정의 하고 있음을 알 수 있습니다. 결국 ASSERT_VALID를 호출하면 AfxAsertValidObject 함수가 호출 됩니다.


#define ASSERT_VALID(pOb)  (::AfxAssertValidObject(pOb, THIS_FILE, __LINE__))


아래는 AfxAssertValidObject 함수의 정의입니다. 우선 보이는 것이 #ifdef _DEBUG 인데, 보시는 봐와 같이 이 함수는 컴파일러가 디버그 모드일 때만 정의 되도록 되어 있습니다. 결국, 릴리즈 모드로 빌드해서 배포할때는 이 함수는 포함되지 않기 때문에 코드의 양이 많이지는 부담이 전혀 없습니다. 그렇기 때문에 효율적으로 객체를 관리하기 위해서 객체의 메모리와 멤버 변수들이 유효한지 확인하고 싶을때 여기저기 많이 사용한다면 디버깅 할 때 굉장히 유리할 뿐만 아니라 좀 더 견고하고 완성도 높은 프로그램을 개발하는데 많은 도움이 되리라 생각됩니다.

아래 함수의 동작은 인자로 들어 온 pOb에 대한 NULL 검사를 수행한 이후, CObject 클래스의 크기 만큼 메모리 블록이 유효한지 검사를 합니다. 다음으로 객체의 메모리 블록 최초 4byte를 검사하여 virtual table이 유효한지 검사하며 CRuntimeClass의 m_nObjectSize 멤버 변수를 이용하여 객체의 모든 메모리 영역이 유효한지 검사합니다. 마지막으로 AssertValid함수를 호출하여 객체의 멤버 변수에 대한 유효성 검사를 수행합니다.

#ifdef _DEBUG
void AFXAPI AfxAssertValidObject(const CObject* pOb, LPCSTR lpszFileName, int nLine)
{
 if (pOb == NULL)
 {
  TRACE(traceAppMsg, 0, "ASSERT_VALID fails with NULL pointer.\n");
  if (AfxAssertFailedLine(lpszFileName, nLine))
   AfxDebugBreak();
  return;     // quick escape
 }
 if (!AfxIsValidAddress(pOb, sizeof(CObject)))
 {
  TRACE(traceAppMsg, 0, "ASSERT_VALID fails with illegal pointer.\n");
  if (AfxAssertFailedLine(lpszFileName, nLine))
   AfxDebugBreak();
  return;     // quick escape
 }

 // check to make sure the VTable pointer is valid
 ASSERT(sizeof(CObject) == sizeof(void*));
 if (!AfxIsValidAddress(*(void**)pOb, sizeof(void*), FALSE))
 {
  TRACE(traceAppMsg, 0, "ASSERT_VALID fails with illegal vtable pointer.\n");
  if (AfxAssertFailedLine(lpszFileName, nLine))
   AfxDebugBreak();
  return;     // quick escape
 }

 if (!AfxIsValidAddress(pOb, pOb->GetRuntimeClass()->m_nObjectSize, FALSE))
 {
  TRACE(traceAppMsg, 0, "ASSERT_VALID fails with illegal pointer.\n");
  if (AfxAssertFailedLine(lpszFileName, nLine))
   AfxDebugBreak();
  return;     // quick escape
 }
 pOb->AssertValid();
}


그럼, 위에서 처럼 AssertValid() 함수만 호출 되면 객체의 멤버변수가 유효한지 자동으로 검사를 해 줄까요? 전혀 그렇지 않습니다. 아래는 CObject 클래스의 AssertValid 함수의 선언과 정의입니다. 보시는 봐와 같이 AssertValid 함수는 virtual로 선언 되어있습니다. 이는 다형성을 이용하여 CObject로 부터 파생되는 클래스가 AssertValid 함수를 재정의 하도록 하여 객체 멤버변수에 대한 유효성 검사를 수행하도록 한 함수입니다. CObject로 부터 파생된 클래스는 자신의 부모 클래스의 AssertValid를 호출 한 후 자신의 멤버변수에 대한 유효성 검사를 수행하면 됩니다. CObject클래스의 AssertValid 함수의 정의를 보면 자기 자신의 객체에 대한 NULL 검사를 수행하고 있는 것을 확인하실 수 있습니다.

#if defined(_DEBUG) || defined(_AFXDLL)
 // Diagnostic Support
 virtual void AssertValid() const;
 virtual void Dump(CDumpContext& dc) const;
#endif

void CObject::AssertValid() const
{
 ASSERT(this != NULL);
}

위 설명을 기초로 하여 간단히 ASSERT_VALID 매크로를 사용하는 예제 코드를 만들어 보았습니다.

// ASSERT_VALID.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
class Base : public CObject
{
 //
 // CRuntimeClass를 선언합니다.
 //
 DECLARE_DYNAMIC(Base)
 int i;
 
public:
 Base(int i) { this->i = i; }
 virtual ~Base() {}
#ifdef _DEBUG
 virtual void AssertValid() const
 {
  //
  // 자신의 상위 클래스의 AssertValid함수를 호출하여
  // 상위 클래스의 멤버 변수에 대한 유효성 검사를 수행합니다.
  //
  CObject::AssertValid();
  ASSERT(i > 0);
 }
#endif
};
//
// CRuntimeClass를 정의합니다.
//
IMPLEMENT_DYNAMIC(Base, CObject)
class Derived : public Base
{
 //
 // CRuntimeClass를 선언합니다.
 //
 DECLARE_DYNAMIC(Derived)
 int j;
public:
 Derived(int i, int j) : Base(i) { this->j = j; }
 virtual ~Derived() {};
#ifdef _DEBUG
 virtual void AssertValid() const
 {
  //
  // 자신의 상위 클래스의 AssertValid함수를 호출하여
  // 상위 클래스의 멤버 변수에 대한 유효성 검사를 수행합니다.
  //
  Base::AssertValid();
  ASSERT(j > 0);
 }
#endif
};
//
// CRuntimeClass를 정의합니다.
//
IMPLEMENT_DYNAMIC(Derived, Base)

int _tmain(int argc, _TCHAR* argv[])
{
 Base* p = new Derived(-1, -1);
 ASSERT_VALID(p);
 delete p;
 return 0;
}
 

* 실행 결과

위 예제 코드를 보시면 각 클래스의 멤버 변수는 0보다 큰 값을 가져야 함을 알 수 있습니다. 하지만 생성자에서는 -1을 각각 멤버 변수에 할당해 두었습니다. 결국 프로그램을 실행하면 아래와 같이 ASSERT 창이 뜨며 문제가 발생한 파일 명과 라인 넘버를 출력해 줍니다. 물론 Debug 모드에서 실행을 하셔야합니다 ~ ^^
    
댓글 로드 중…

최근에 게시된 글