2008년 12월 22일
AFX_ZERO_INIT_OBJECT()
몇일 전 공부를 하다가 AFX_ZERO_INIT_OBJECT() 라는 매크로 함수를 보았습니다. MSDN을 찾아 보았지만 MSDN에도 나오지 않은 매크로 입니다. 구현 코드를 보면 다음과 같습니다.

#define AFX_ZERO_INIT_OBJECT(base_class) \
    memset(((base_class*)this)+1, 0, sizeof(*this) - sizeof(class base_class));

그러니까 ... memset() 함수를 이용해서 Base 클래스와 Derived 클래스가 서로 상속 관계에 있을 때 Base 클래스의 멤버는 그대로 두고 Derived 클래스의 멤버만 0으로 초기화 시켜주는 매크로 입니다.

간단히 예제를 만들어서 위 매크로의 동작을 살펴보면 아래와 같습니다.


// AFX_ZERO_INIT_OBJECT.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#define AFX_ZERO_INIT_OBJECT(base_class) \
    memset(((base_class*)this)+1, 0, sizeof(*this) - sizeof(class base_class));

using namespace std;

class Base
{
public:

    int i;
    int j;
};

class Derived : public Base
{
public:
    int k;
    int l;

    Derived()
    {
        AFX_ZERO_INIT_OBJECT(Base);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Derived derived;

    cout<<derived.i<<endl;
    cout<<derived.j<<endl;
    cout<<derived.k<<endl;
    cout<<derived.l<<endl;

    return 0;
}

* 실행 결과 ..

실행 결과를 보시면 Base 클래스의 멤버는 초기화 하지 않고 Derived 클래스의 멤버만 초기화 했음을 알 수 있습니다. AFX_ZERO_INIT_OBJECT 매크로 함수의 동작 원리는 아래의 그림과 같습니다. 위에 빨간 점선 박스가 Base class이고 파란색 점선 박스가 Derived class를 나타냅니다.

그런데, 이 매크로 함수가 왜 만들어졌는지는 저도 정확히는 모르겠습니다만, Nibu tomas님의 bits and byte 블로그의 Careful with memset and ZeroMemory! 라는 제목의 포스팅 중 아래 내용을 보시면 어느정도 이 매크로가 존재하는 이유를 유추할 수 있을 것 같습니다.

I read in a blog sometime back that this technique( {0} ) reduces code readability, well I am yet to face a code readability issue!

struct BookInfo
{
    int nID;
    TCHAR szName[256];
};

C의 구조체를 예를 들어 위와 같은 구조체가 있다고 할 때 BookInfo bookInfo = {0, 0}; 이와 같은 방법으로 초기화 해주는 것이 코드의 양을 줄여 준다는 것입니다. 결국 클래스를 이용할 때 생성자에서 memset(this, 0, sizeof(*this)) 이렇게 해주면 일일이 멤버변수를 지정해서 초기화 해주는 것 보다 코드의 양을 줄일 수 있고 간편하며 메모리를 직접 초기화 하기 때문에 속도도 빠르다고 할 수 있습니다. 예를 들면 아래와 같이 구현하는 것입니다.

class BookInfo
{
    int nID;
    TCHAR szName[256];

    BookInfo()
    {
        memset(this, 0, sizeof(*this));
    }
};

하지만 이 방법에는 치명적인 함정이 숨어 있는데 아래의 Jijo.Raj님의 Visual C++ Tips 블로그의 내용을 보면 memset 함수를 사용 해서 클래스의 멤버를 초기화 할 경우 객체의 메모리 최초 4byte가 가리키고 있는 virtual table의 메모리 주소를 overwrite 할 위험성이 있다고 나와 있습니다. 이를 예방하기 위해서 MFC에서는 AFX_ZERO_INIT_OBJECT() 매크로 함수를 정의해 놓았다고 합니다. 하지만 이 매크로 함수를 사용할 때도 주의 할 것이 있는데 클래스의 멤버로 다른 클래스의 객체가 있어서는 안된다는 것입니다. 이유는 멤버변수로 가지고 있는 클래스의 객체들의 virtual table의 메모리 주소를 overwrite 할 수 있기 때문이라고 하네요 ~~


Memset on objects will result in crash? Then none of your MFC apps will work.

25 03 2008

Icon Description


While stepping from C to the C++ world, it’s the first pitfall that we encounter – initializing an object with memset. Since memset if faster,we used to initialize objects like this,

CObject obj;
memset( &obj, 0, sizeof(obj));

More than enough for a crash! Because if the class contains virtualfunctions, then the first 4 bytes holds the pointer to vtable. If weuse memset, then the vtable pointer will be overwritten to 0 and it mayend up in crash.

But, do you know one thing? The famous MFC framework uses memset toinitialize some of its objects such as CDialog, CScrollView etc. Can’tbelieve? It does, but safely. Without damaging the vtable pointer.


icon_underthehood.jpg


If you check the constructor of CDialog, you can see one macro - AFX_ZERO_INIT_OBJECT();. This macro calls memset internally. See the implementation of macro below.

// zero fill everything after the vtbl pointer
#define AFX_ZERO_INIT_OBJECT(base_class) \
memset(((base_class*)this)+1, 0, sizeof(*this) - sizeof(class base_class));

But it’s safe. Because it skips the vtable ptr.


Icon Note


Don’t try this macro in your source. Because CDialog doesn’t containany objects as members. It contains just some pointers and handles. Ifyour object contain some class variables as members which containvirtual table, then that vtable ptr may be overwritten. Take care!



위 글의 내용에 따라 간단한 예제를 만들고 실행해 보았더니 어김없이 메모리 참조에 대한 에러가 발생합니다.
// AFX_ZERO_INIT_OBJECT.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#define AFX_ZERO_INIT_OBJECT(base_class) \
    memset(((base_class*)this)+1, 0, sizeof(*this) - sizeof(class base_class));

using namespace std;

class Base
{
public:

    int i;
    int j;

    Base()
    {
        memset(this, 0, sizeof(*this));
    }
    virtual ~Base()
    {
        cout<<"~Base()"<<endl;
    }
    virtual void Print()
    {
        cout<<"i : "<<i<<"\tj : "<<j<<endl;   
    }
};

class Derived : public Base
{
public:
    int k;
    int l;

    Derived()
    {
        //AFX_ZERO_INIT_OBJECT(Base);
        memset(this, 0, sizeof(*this));
    }
    ~Derived()
    {
        cout<<"~Derived()"<<endl;
    }

    void Print()
    {
        cout<<"i : "<<i<<"\tj : "<<j<<endl;   
        cout<<"k : "<<k<<"\tl : "<<l<<endl;
    }
};



int _tmain(int argc, _TCHAR* argv[])
{
    Base* base = new Derived;
   
    base->Print();

    delete base;

    return 0;
}


* 실행 결과 ..


* 참고 사이트

Memset on objects will result in crash? Then none of your MFC apps will work.

Careful with memset and ZeroMemory!

이 글과 관련있는 글을 자동검색한 결과입니다 [?]

by greenfrog | 2008/12/22 20:21 | C++ / WIN32 / MFC | 트랙백 | 덧글(2)
트랙백 주소 : http://greenfrog7.egloos.com/tb/1261919
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by Jijo at 2008/12/24 01:13
Hello GreenFrog,

Thanks for mentioning my article in your blog! Your explanation is nice with lot of pictorial explanations! Keep rocking and wish you all the best!

Regards,
Jijo.
Commented by greenfrog at 2008/12/24 08:41
Your article was a great help to me. Thank you.

:         :

:

비공개 덧글



<< 이전 페이지 | 다음 페이지 >>