파괴자를 직접 부르면 어떻게 될까?

object o;

o.~object();

궁금해졌다.

-----------------------------------------------------------------------------
#include <windows.h>
#include <tchar.h>

#include <iostream>

class o_1
{
public:
 o_1 ()         { std::cout << "o_1()"  << std::endl; }
 virtual ~o_1() { std::cout << "~o_1()" << std::endl; }
};

class o_2 : public o_1
{
public:
 o_2 () : o_1() { std::cout << "o_2()"  << std::endl; }
 virtual ~o_2() { std::cout << "~o_2()" << std::endl; }
};

class o_3 : public o_2
{
public:
 o_3 () : o_2() { std::cout << "o_3()"  << std::endl; }
 virtual ~o_3() { std::cout << "~o_3()" << std::endl; }
};

void test1 (void)
{
 o_3  o;

 o_1* p = &o;

 std::cout << "-----------" <<std::endl;
 p->~o_1 ();
 std::cout << "-----------" <<std::endl;
}

void test2 (void)
{
 o_1* p = new o_3 ();

 std::cout << "-----------" <<std::endl;
 p->~o_1 ();
 std::cout << "-----------" <<std::endl;

 delete p;
}

void test3 (void)
{
 o_1* p = new o_3 ();

 std::cout << "-----------" <<std::endl;
 //p->~o_1 ();
 std::cout << "-----------" <<std::endl;

 delete p;
}

int _tmain(int argc, _TCHAR* argv[])
{
 std::cout << "###########" <<std::endl;
 test1();
 std::cout << "###########" <<std::endl;
 test2();
 std::cout << "###########" <<std::endl;
 test3();
 std::cout << "###########" <<std::endl;

 return 0;
}
-----------------------------------------------------------------------------
###########
<- test1() 함수: 잘된다...
o_1()
o_2()
o_3()
-----------
~o_3()
~o_2()
~o_1()
-----------
~o_3()
~o_2()
~o_1()
########### <- test2() 함수: 무엇인가 이상하다???
o_1()
o_2()
o_3()
----------- <- p->~o_1 (); 호출 부분... 부모 클래스 파괴자를 호출한다.
~o_3()
~o_2()
~o_1()
----------- <- delete p; 가 이상하다 = _=??? 무엇일까...
~o_1()
########### <- test3() 함수:
o_1()
o_2()
o_3()
-----------
<- //p->~o_1 ();
-----------   
주석처리 했더니 delete p; 가 제대로 동작한다.
~o_3()
~o_2()
~o_1()
###########
계속하려면 아무 키나 누르십시오 . . .
-----------------------------------------------------------------------------
디버깅을 해보았다.

p->~o_1(); 호출하기전 __vfptr(virtual fucntion pointer) 녀석은 이렇게 가리키고 있었다.

사용자 삽입 이미지

하지만, p->~o_1();을 호출하니 __vfptr 녀석은 이렇게 바뀌어 버렸다.
사용자 삽입 이미지

         test1()역시 p->~o_1();를 수행하면 p의 __vfptr 값도 동일하게 바뀌었다.               


고심끝에 내린 결론은

test1()에서 Stack에 생성된 객체는 파괴될때 가상함수-파괴자를 사용하지 않고 파괴 되므로
위와같은 결과를 얻었고,

test2()에서 Heap에 생성된 객체는 파괴되기전에 직접 p->~o_1(); 호출 하여 __vfptr이
변경됨으로서 delete p;에서 가상함수-파괴자를 이용한 파괴자 호출이 제대로 되지 못하는
것이다.

__vfptr이 변경 되므로 가상함수들이 제대로 동작을 안할 것이라 판단 되었다.
그래서, 소스 코드를 수정해서 다시 테스트 해보았다.
-----------------------------------------------------------------------------
#include <windows.h>
#include <tchar.h>

#include <iostream>

class o_1
{
public:
 o_1 ()         { std::cout << "o_1()"  << std::endl; }
 virtual ~o_1() { std::cout << "~o_1()" << std::endl; }
public:
 virtual void show (void)
 {
  std::cout << "o_1::show()" << std::endl;
 }

};

class o_2 : public o_1
{
public:
 o_2 () : o_1() { std::cout << "o_2()"  << std::endl; }
 virtual ~o_2() { std::cout << "~o_2()" << std::endl; }
public:
 virtual void show (void)
 {
  std::cout << "o_2::show()" << std::endl;
 }

};

class o_3 : public o_2
{
public:
 o_3 () : o_2() { std::cout << "o_3()"  << std::endl; }
 virtual ~o_3() { std::cout << "~o_3()" << std::endl; }
public:
 virtual void show (void)
 {
  std::cout << "o_3::show()" << std::endl;
 }

};

void test1 (void)
{
 o_3  o;

 o_1* p = &o;

 std::cout << "-----------" <<std::endl;
 p->show ();
 o.show();
 std::cout << "-----------" <<std::endl;
 p->~o_1 ();
 std::cout << "-----------" <<std::endl;
 p->show ();
 o.show();
 std::cout << "-----------" <<std::endl;
}

void test2 (void)
{
 o_1* p = new o_3 ();

 std::cout << "-----------" <<std::endl;
 p->~o_1 ();
 std::cout << "-----------" <<std::endl;

 delete p;
}

void test3 (void)
{
 o_1* p = new o_3 ();

 std::cout << "-----------" <<std::endl;
 //p->~o_1 ();
 std::cout << "-----------" <<std::endl;

 delete p;
}

int _tmain(int argc, _TCHAR* argv[])
{
 std::cout << "###########" <<std::endl;
 test1();
 std::cout << "###########" <<std::endl;
 test2();
 std::cout << "###########" <<std::endl;
 test3();
 std::cout << "###########" <<std::endl;

 return 0;
}
-----------------------------------------------------------------------------
########### <- test1() 함수: 잘되는게 아니었다.
o_1()
o_2()
o_3()
-----------
o_3::show() <- p->show (); 정상이다.
o_3::show() <- o.show();   정상이다.
-----------
~o_3()
~o_2()
~o_1()
-----------
o_1::show() <- p->show ();      __vfptr이 변경되었으므로 제대로 동작을 못한다.     
o_3::show() <- o.show();
-----------
~o_3()
~o_2()
~o_1()
########### <- test2() 함수:
o_1()
o_2()
o_3()
-----------
~o_3()
~o_2()
~o_1()
-----------
~o_1()
########### <- test3() 함수:
o_1()
o_2()
o_3()
-----------
-----------
~o_3()
~o_2()
~o_1()
###########
계속하려면 아무 키나 누르십시오 . . .

-----------------------------------------------------------------------------
처음에는 컴파일러 버그라고 생각되었지만 곰곰히 따져보니 아닌 것 같다.
설마, 진짜 컴파일러 버그는 아니겠지... = _ =;

[사용 컴파일러]
Microsoft Visual Studio 2005
Version 8.0.50727.762  (SP.050727-7600)
Microsoft .NET Framework
Version 2.0.50727 SP1

Installed Edition: Professional

-----------------------------------------------------------------------------
추가:
데브피아에 올렸보았었는데 오해하실 수 있는 부분 있는거 같아서 추가합니다.

제가 말하고 싶었던것은 소멸자를 직접 호출했을경우 vftable을 가리키는 __vfptr이
변경된다는 걸 말하고 싶었던겁니다.

좀 더 부연설명을 하자면 메모리 풀 같은 걸 만들때 메모리 풀에서 받은 객체를 사용하고 삭제
후 그대로 또 사용했을 경우 일어나는 현상이 어떻게 될것인가 따져보는 의미입니다.

위의 코드는 일종의 toy code입니다.


제가 컴파일러 버그가 아닐까라고 생각한 부분은 직접 파괴자(소멸자)가 호출된다고 해서
메모리에서 실제로 delete가 일어나는 건 아니기 때문에 vftable을 가리키는 __vfptr가
변경되는게 맞는건지 아닌지
잘 판단이 안되었기 때문에 적어 놓은겁니다.


 

Posted by 셈말짓기 :