2011년 9월 10일 토요일

C언어 포인터 개념 III - struct - C++ 포인터 Linked-List

C언어의 포인터 이야기C언어 포인터 개념 I
C언어 포인터 개념 II - float,double,char, string 포인터
C언어 포인터 개념 III - struct 포인터 & void 포인터
C언어 포인터 개념 III - struct - C++ 포인터 Linked-List 
C언어 포인터 개념 III - struct 포인터 2


C++  class 포인터 - 리스트 


이것은 C++의 class 구조에서도 같은 구조를 갖는다.

*** LinkedList.h :

class CLinkedList
{
public:
     CLinkedList *next;
     CLinkedList *prev;

     unsigned int    stype;

public:
    CLinkedList(int);
    virtual ~CLinkedList(void);
   
    static CLinkedList *lroot;
    static CLinkedList *ltail;
    static CLinkedList *CLinkedList::getroot() { return CLinkedList::lroot; }
    static CLinkedList *CLinkedList::gettail() { return CLinkedList::ltail; }
    static void CLinkedList::PrintList();
    static void CLinkedList::clearlist() { CLinkedList::lroot = CLinkedList::ltail = NULL; }

    CLinkedList* inserttail();
    CLinkedList* inserttail(CLinkedList *);
    CLinkedList* deletelist(CLinkedList *pmem);
    CLinkedList* deletelist();
};

CLinkedList *CLinkedList::lroot = NULL;
CLinkedList *CLinkedList::ltail = NULL;


*** LinkedList.c :

CLinkedList::CLinkedList(int type)
{
    next = prev = NULL;
    stype = type;
    printf("생성자 : CLinkedList = 0x%08X\n", this);
}
CLinkedList::~CLinkedList(void)
{
 deletelist();
    printf("소멸자 : ~CLinkedList = 0x%08X\n", this);
}

CLinkedList* CLinkedList::inserttail()  // Linked-List의 끝에 연결 하기 - tail
{
    if (CLinkedList::lroot == NULL) {
       CLinkedList::lroot = this;
       CLinkedList::ltail= this;
       next = NULL;
       prev = NULL;
       return this;
    }

    CLinkedList::ltail->next = this;
    next = NULL;
    prev = CLinkedList::ltail;
    CLinkedList::ltail = this;
    return this;
}

CLinkedList* CLinkedList::inserttail(CLinkedList *pmem)  // Linked-List의 끝에 연결 하기 - tail
{
    if (CLinkedList::lroot == NULL) {
       CLinkedList::lroot = pmem;
       CLinkedList::ltail= pmem;
       pmem->next = NULL;
       pmem->prev = NULL;
       return pmem;
    }

    CLinkedList::ltail->next = pmem;
    pmem->next = NULL;
    pmem->prev = CLinkedList::ltail;
    CLinkedList::ltail = pmem;
    return pmem;
}
*** Human.h :

class CHuman : public CLinkedList
{
public:
   char id[8];
   char name[8];
   char sex;
   int  age;
   char tel[8];


public:
  CHuman(int);
  ~CHuman(void);

  void print();
};

*** Member.h :


class CMember : public CHuman
{
public:

   char   *pos;
   int     year;

   void   *data;
   int    szdata;


public:
   CMember(void);
   ~CMember(void);


   ///////////////////////////////////////
   // Member Functions


   void print();
   void deletemalloc();
   void deletedata();

};

*** Customer.h :

class CCustomer : public CHuman
{
public:
   char   *from;
   void   *data;
   int    szdata;
public:
 CCustomer(void);
 ~CCustomer(void);
 void print();
    void deletemalloc();
 void deletedata();
};

C++에서 상속을 받으면 상속을 받은 부모 클래스가 먼저 오고 child 클래스의 멤버변수가 다음에 온다. 따라서 struct와 시슷한 구조로 나온다.

struct의 구조와 클래스의 멤버변수가 존재하는 구조는 같다. 단, 다음과 같은 virutal 함수를 처리하기 위한 테이블 4바이트가 추가 되는 것이 다르다. 만약 다음 함수에서

virtual ~CHuman(void);

virtual 이  없다면 struct와 구조가 같다.

처럼 virtual을 사용하면 next와 prev 이전에 vitual table 4바이트가 먼저 온다. 나중에 소멸자가 호출이 제대로 되도록 하기 위해서는 어쩔수 없다.

int main()
{

    CLinkedList::clearlist(); --> 리스트의 root와 tail 변수를 초기화

      pmem = new CMember();
      // 클래스의 인스턴스의 변수에 값을 설정 한다.
      pmem->inserttail();     ---> 리스트에 연결해 준다.
  
     // 다른 member들을 만들고, 연결한다.

    // 해당 인스던스를 사용하고 . .

    // 사용이 끝나면 지워야지...
    // 리스트에서만 지우고 싶으면
     pcust->deletelist();

   // 만약 object의 인스턴스가 사용을 한 후 필요가 없으면
    delete pcust;  -->  소멸자에 리스트 연결을 끊는 함수 호출 deletelist()

   deletelist(); --> 모든 리스트를 지운다.

}

C++이 struct와 같은 개념으로 자료를 배치하고 있다는 사실을 알면 object을 다루는 일이 쉬워 진다.

첨부된 파일을 실행하면

생성자 : CLinkedList = 0x00395C90
생성자 : CHuman = 0x00395C90
생성자 : CMember = 0x00395C90
생성자 : CLinkedList = 0x00393900
생성자 : CHuman = 0x00393900
생성자 : CCustomer = 0x00393900
생성자 : CLinkedList = 0x00393958
생성자 : CHuman = 0x00393958
생성자 : CMember = 0x00393958

[0x004043EC] CLinkedList::lroot = 0x00395C90
[0x004043F0] CLinkedList::ltail = 0x00393958

[MEMBER] pman= 0x00395C90
[0x00395C90] vftable* = 0x004033F0
CHuman this = 0x00395C90
[0x00395C94] next = 0x00393900
[0x00395C98] prev = 0x00000000

[0x00395C9C] stype = 0x00000001
[0x00395CA0] id = 34556
[0x00395CA8] name = 이순신
[0x00395CB0] sex = M
[0x00395CB4] age = 45
[0x00395CB8] tel = 파발마1
CMember this = 0x00395C90
[0x00395CC0] pos  = [0x0040340C] 통제사
[0x00395CC4] year = [1597]

[CUSTOMER] pman= 0x00393900
[0x00393900] vftable* = 0x004031C4
CHuman this = 0x00393900
[0x00393904] next = 0x00393958
[0x00393908] prev = 0x00395C90

[0x0039390C] stype = 0x00010402
[0x00393910] id = 01111
[0x00393918] name = 선조
[0x00393920] sex = M
[0x00393924] age = 54
[0x00393928] tel = 파발마0
CCustomer this = 0x00393900
[0x00393930] from = [0x0040342C] [왕]
[0x00393934] data = [0x00395CE8] 왜적을 섬멸하라
[0x00393938] szdata = 1024

[MEMBER] pman= 0x00393958
[0x00393958] vftable* = 0x004033F0
CHuman this = 0x00393958
[0x0039395C] next = 0x00000000
[0x00393960] prev = 0x00393900

[0x00393964] stype = 0x00000001
[0x00393968] id = 01233
[0x00393970] name = 권준
[0x00393978] sex = M
[0x0039397C] age = 28
[0x00393980] tel = 파발마2
CMember this = 0x00393958
[0x00393988] pos  = [0x00403458] 순천부사
[0x0039398C] year = [1537]

리스트 만을 지웠다가 다시 연결하기...
List : 0x00395C90  => 0x00393900  => 0x00393958  => 0x00000000
List 지우기 : deletelist(0x00393900)
deletelist = 0x00393900
List : 0x00395C90  => 0x00393958  => 0x00000000

다시 연결하기 : deletelist(0x00393900)
List : 0x00395C90  => 0x00393958  => 0x00393900  => 0x00000000

Delete Class : delete pcust[0x00393900]
소멸자 : ~CCustomer = 0x00393900
소멸자 : ~CHuman = 0x00393900
deletelist = 0x00393900
소멸자 : ~CLinkedList = 0x00393900

Delete All List: deletelist()
List : 0x00395C90  => 0x00393958  => 0x00000000
소멸자 : ~CMember = 0x00395C90
소멸자 : ~CHuman = 0x00395C90
deletelist = 0x00395C90
소멸자 : ~CLinkedList = 0x00395C90
List : 0x00393958  => 0x00000000
소멸자 : ~CMember = 0x00393958
소멸자 : ~CHuman = 0x00393958
deletelist = 0x00393958
소멸자 : ~CLinkedList = 0x00393958
List : 0x00000000

실행된 결과의 데이터 구조를 그리면



struct 구조와 다른 것은 virtual 소멸자로 인한 virtual function table이 맨앞에 추가되다는 것이다. 이 테이블은 클래스 멤버함수의 테이블의 위치를 나타내는 포인터 이다. 따라서 모든 포인터는 32비트 CPU에서 4바이트이다. 그리고 다음에 struct 처럼 classs 멤버 변수가 나열 된다.
만약 virtual 소멸자를 없애면 맨앞의 virtual-function-table 포인터가 필요없다. 그러면 struct와 구조가 같다.


class CLinkedList
{
public:
   // virtual
   ~CLinkedList(void);
   . . .
};

int main()
{
    CHuman*  pman = (CHuman*)  new CMember();
    delete pman;
    ...
}

실행 결과는 다음과 같다.

소멸자 : ~CHuman = 0x00395B80
소멸자 : ~CLinkedList = 0x00395B80

처럼 ~CMember()가 호출되지 않는다.
이렇게 되면 경우에 따라 소멸자 호출에 문제를 일으킨다.

이에 반해 virtual 함수를 사용하면
소멸자 : ~CMember= 0x00393900
소멸자 : ~CHuman = 0x00393900
소멸자 : ~CLinkedList = 0x00393900

정상적으로 소멸자가 호출 된다.

virtual이 없을 때는 다음과 같은 포인터라면 당연히 제대로 호출 된다.
    CMember*  pmem = new CMember();
    delete pmem;

소멸자 : ~CMember= 0x00393900
소멸자 : ~CHuman = 0x00393900
소멸자 : ~CLinkedList = 0x00393900
포인터가 현재 어떤 class인가를 알기 때문에 상속의 모든 소멸자가 호출 된다.

virtual은 부모 클래스로 포인터 관리할 때는 현재 인스턴스가 자식 클래스인지 아닌지 알 수가 없어서 이것을 원래 생성된 형태로 관리하도록 함수 구성을 한것이다.


크리에이티브 커먼즈 라이선스


댓글 없음:

댓글 쓰기