2011년 9월 10일 토요일

C언어 포인터 개념 III - struct 포인터 2

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


다른 struct간의 linked-list로 연결하기

struct 포인터를 사용 한 linked-list는 같은 struct에서만 가능한 것은 아니다. 다르게 선언된 struct에서도 포인터를 지정할 수 있다. 쉬운 방법은 여러가지의 struct 선언을 하고 타입변환을 통해 원하는 프로그램 모듈을 하는 방법이 있다. 각각의 struct에서 링크에 필요한 포인터 만을 앞에 통일화 시키고 나머지 다른 부분의 구조화를 연결하는 방식이다. 위에서 양방향 linked-list는 연결용 2개의 변수를 사용 하였다.
다음과 같은 두가지를 struct을 생각해 보자.
  
struct Member {
   struct Member *next;
   struct Member *prev;
   int    stype;
   char   id[8];
   struct Man man;
   char   *pos;
   int     year;
   void   *data;
   int    szdata;
};

struct Customer {
   struct Customer *next;
   struct Customer *prev;
   int    stype;
   char   id[8];
   struct Man man;
   char   *from;
   void   *data;
   int    szdata;
};

struct Member와 Customer는 다른 struct 구조를 갖는다. 이것끼리 연결하려면 struct 시작에 next와 prev변수를 우선 선언 하였다. 이렇게 하면 두개의 struct의 시작 번지부터 next와 prev 변수가 배치 된다. 이렇게 하면 쉽게 이 두개의 struct을 연결할 수 있다.

우선 각각의 필요한 struct을 동적으로 malloc()/new을 이용하여 만들 수 있다. 이 두개의 변수 공간에 앞에 next와 prev가 존재하므로 이것을 각각의 연결 주소값을 설정하면 된다.

여러가지의 struct을 사용하면 연결 프로그램을 각각 작성해야 하므로 연결용 struct을 다시 선언 한다.

typedef struct LinkedList {
   struct LinkedList *next;
   struct LinkedList *prev;
} LinkedList;

이 두개의 변수next와 prev을 사용하면 된다. 각각의 실제 공간을 차지하는 Member와 Customer의 구조를 이 LinkedList 로 매핑하면 된다. 이것으로 포인터 조작을 하면 연결이 완성 된다.

*** linkedlist.h *************************************************
#ifndef _LINKEDLIST_H
# define _LINKEDLIST_H

typedef struct LinkedList {
   struct LinkedList *next;
   struct LinkedList *prev;
} LinkedList;
////////////////////////////////////////////////////////
// Member Function
struct LinkedList *getroot();
struct LinkedList *gettail();

void init_LinkedList();
struct LinkedList *inserttail( struct LinkedList *pmem);
struct LinkedList *deletelist( struct LinkedList *pmem);
#endif

*** member.h *************************************************
#ifndef _MEMBER_H
# define _MEMBER_H

struct Man {
   char name[8];
   char sex;
   int  age;
   char tel[8];
};
struct Human {
   struct Human *next;
   struct Human *prev;
   int    stype;
   char   id[8];
   struct Man man;
   int     data;     ---> 데이터가 있는 위치를 알기 위한 변수, alignment을 고려하여 int으로.
};

struct Member {
   struct Member *next;
   struct Member *prev;
   int    stype;
   char   id[8];
   struct Man man;
   char   *pos;
   int     year;
   void   *data;
   int    szdata;
};
struct Customer {
   struct Customer  *next;
   struct Customer  *prev;
   int    stype;
   char   id[8];
   struct Man man;
   char   *from;
   void   *data;
   int    szdata;
};

////////////////////////////////////////
// struct의 타입 정의
// 다음 타입은 각 struct의 int stype을 이용한다.
#define MEMTYPE_MEBER     0x0001   // struct Member
#define MEMTYPE_CUSTOMER  0x0002   // struct Customer
// 내부 데이터의 생성
#define DTYPE_POS_MALLOC    0x0100 // struct Member에서   pos가 malloc해서 만들었을 때
#define DTYPE_FROM_MALLOC   0x0200 // struct Customer에서 from가 malloc해서 만들었을 때
#define DTYPE_DATA_MALLOC   0x0400 // void *data가 malloc해서 만들었을 때
#define DTYPE_DATA_STRING   0x10000 // void *data가 malloc해서 만들었을 때
///////////////////////////////////////
// Member Functions
void printlist();
void deletelist();
void deletemalloc(struct Human *pman);
#endif


*** linkedlist.c *************************************************

#include "linkedlist.h"

struct LinkedList *lroot;
struct LinkedList *ltail;
struct LinkedList *getroot() { return lroot; }
struct LinkedList *gettail() { return ltail; }

void init_LinkedList()
{
     lroot = NULL;
     ltail= NULL;
}
struct LinkedList *inserttail( struct LinkedList *pmem)  // Linked-List의 끝에 연결 하기 - tail
{
    if (lroot == NULL) {
       lroot = pmem;
       ltail= pmem;
    pmem->next = NULL;
       pmem->prev = NULL;
       return pmem;
    }
    ltail->next = pmem;
    pmem->next = NULL;
    pmem->prev = ltail;
    ltail = pmem;
    return pmem;
}
struct LinkedList *deletelist( struct LinkedList *pmem)
{
    if  (pmem->prev ==  NULL) {
       lroot = pmem->next;
    } else {
       pmem->prev->next = pmem->next ;
    }
    if (pmem->next == NULL) {
        ltail =  pmem->prev ;
    } else {
        pmem->next->prev = pmem->prev;
    }   
  
    pmem->next = NULL;
    pmem->prev = NULL;
    return pmem;
}


*** main.c *************************************************

#include <string.h>
#include <malloc.h>

#include "linkedlist.h"
#include "member.h"

 struct Member   *pmem1;
 struct Customer *pmem2;
 struct Member   *pmem3;

int main(int argc, char* argv[])
{
    struct Member *pmem;
    struct Customer *pcust;

     init_LinkedList();

     pmem1 = pmem = (struct Member *) malloc( sizeof(struct Member));   // 원하는 멤버를 만든다.
     pmem->stype = MEMTYPE_MEBER;

     // struct의 값들을 설정 한다.
     strcpy(pmem->id, "34556");
     strcpy(pmem->man.name, "이순신");
     pmem->man.sex = 'M';
     pmem->man.age = 45;
     strcpy(pmem->man.tel, "파발마1");
     pmem->pos = "통제사";
     pmem->year = 1597;
     pmem->data = NULL;
     pmem->szdata = 0;

     inserttail((LinkedList*) pmem);


     pmem2 = pcust = (struct Customer *) malloc( sizeof(struct Customer));  //새로운 멤버를 만든다.
     pcust->stype = MEMTYPE_CUSTOMER;
     strcpy(pcust->id, "01111");
     strcpy(pcust->man.name, "선조");
     pcust->man.sex = 'M';
     pcust->man.age = 54;
     strcpy(pcust->man.tel, "파발마0");
     pcust->from = "왕";
     pcust->data = (void*) malloc( 1024 );
     strcpy((char*) pcust->data, "왜적을 섬멸하라");
     pcust->szdata = 1024;
     pcust->stype |= DTYPE_DATA_MALLOC | DTYPE_DATA_STRING;

     inserttail((LinkedList*) pcust);   // Linked-List의 끝에 연결 한다.

     pmem3 =
     pmem = (struct Member *) malloc( sizeof(struct Member));
     pmem->stype = MEMTYPE_MEBER;
     strcpy(pmem->id, "01233");
     strcpy(pmem->man.name, "권준");
     pmem->man.sex = 'M';
     pmem->man.age = 28;
     strcpy(pmem->man.tel, "파발마2");
     pmem->pos = "순천부사";
     pmem->year = 1537;
     pmem->data = NULL;
     pmem->szdata = 0;

     inserttail((LinkedList*) pmem);  // Linked-List의 끝에 연결 한다.
 
     // Linked-List 사용하기 
     printlist();
    
     // 사용이 끝났다면 삭제
     deletelist();

    return 0;
}


기타 프린터 함수

*** member.c *************************************************
#include <stdio.h>
#include <malloc.h>

#include "linkedlist.h"
#include "Member.h"

void printlist()
{
   struct Member   *pmem;
   struct Customer *pcust;
   struct Human    *pman;

    pman = (struct Human *) getroot();

    for (;pman; pman=pman->next) {
        printf("pman [%s]= 0x%08X\n",   pman->stype & MEMTYPE_MEBER?"MEMBER" : "CUSTOMER",
                                 pman);
       printf("pman->next = 0x%08X\n", pman->next);
       printf("pman->prev = 0x%08X\n", pman->prev);
       printf("pman->stype = 0x%08X\n", pman->stype);
       printf("pman->id = %s\n",       pman->id);
       printf("pman->man.name = %s\n", pman->man.name);
       printf("pman->man.sex = %c\n",  pman->man.sex);
       printf("pman->man.age = %d\n",  pman->man.age);
       printf("pman->man.tel = %s\n",  pman->man.tel);

       if (pman->stype & MEMTYPE_MEBER) {
            pmem = (struct Member*) pman;
            printf("pmem->pos = 0x%08X [%s]\n", pmem->pos, pmem->pos);
            printf("pmem->year = 0x%08X [%d]\n",  &pmem->year,   pmem->year);
      
            if (pman->stype & DTYPE_DATA_STRING)
                printf("pmem->data [0x%08X]= %s\n", (char*) pmem->data, (char*) pmem->data);
    
       } else if (pman->stype & MEMTYPE_CUSTOMER) {
           pcust = (struct Customer *) pman;
 
           printf("pcust->from = 0x%08X [%s]\n", pcust->from, pcust->from);
           if (pman->stype & DTYPE_DATA_STRING)
               printf("pcust->data [0x%08X]= %s\n", (char*) pcust->data, (char*) pcust->data);
       }
       //printf("pmem->szdata = %d\n",   pmem->szdata);
       printf("\n" );
    }
}
void deletelist()
{
   struct Human    *pman, *pnext;
    pman = (struct Human *) getroot();
    while (pman) {
        pnext = pman->next;
        deletemalloc(pman);
        free(pman);
        pman = pnext;
    }
}
void deletemalloc(struct Human *pman)
{
   struct Member   *pmem;
   struct Customer *pcust;
  
    if (pman->stype & MEMTYPE_MEBER) {
        pmem = (struct Member*) pman;
    if (pmem->stype & DTYPE_POS_MALLOC) {
       if (pmem->pos)
          free(pmem->pos);
    }
    if (pmem->stype & DTYPE_DATA_MALLOC) {
       if (pmem->data)
          free(pmem->data);
    }
 } else if (pman->stype & MEMTYPE_CUSTOMER) {
     pcust = (struct Customer *) pman;
     if (pcust->stype & DTYPE_FROM_MALLOC) {
         if (pcust->from)
             free(pcust->from);
     }
     if (pcust->stype & DTYPE_DATA_MALLOC) {
        if (pcust->data)
           free(pcust->data);
     }
  }
}

*** 실행하면

pman [MEMBER]= 0x00395B88
pman->next = 0x00395C00
pman->prev = 0x00000000
pman->stype = 0x00000001
pman->id = 34556
pman->man.name = 이순신
pman->man.sex = M
pman->man.age = 45
pman->man.tel = 파발마1
pmem->pos = 0x004157A8 [통제사]
pmem->year = 0x00395BB8 [1597]

pman [CUSTOMER]= 0x00395C00
pman->next = 0x00395C78
pman->prev = 0x00395B88
pman->stype = 0x00010402
pman->id = 01111
pman->man.name = 선조
pman->man.sex = M
pman->man.age = 54
pman->man.tel = 파발마0
pcust->from = 0x00415788 [왕]
pcust->data [0x00393190]= 왜적을 섬멸하라

pman [MEMBER]= 0x00395C78
pman->next = 0x00000000
pman->prev = 0x00395C00
pman->stype = 0x00000001
pman->id = 01233
pman->man.name = 권준
pman->man.sex = M
pman->man.age = 28
pman->man.tel = 파발마2
pmem->pos = 0x0041574C [순천부사]
pmem->year = 0x00395CA8 [1537]

main 함수에서 linked-list의 root와 tail 변수을 초기화 한다.

그리고 원하는 struct을 malloc을 통해 생성 한다. 생성된 변수에 원하는 값을 넣고
linked-list 연결 함수를 호출 한다. 이것은 단지 각각 struct의 next,prev 변수를 이용하여 연결을 만드는 작용을 한다.

pmem = (struct Member *) malloc( sizeof(struct Member)); ---> struct 생성

inserttail((LinkedList*) pmem); ---> linked list에 연결

연결 함수 inserttail은 다양한 struct을 수용하기 위한 연결 포인트 만으로 재선언된 struct이다.  Memeber 뿐만 아니라

pcust = (struct Customer *) malloc( sizeof(struct Customer));
inserttail((LinkedList*) pcust);

Member와 Customer는 모두 LinkedList struct로 변환되어 져야 linked-list을 만드는 동일한 함수를 사용할 수 있기 때문이다.

이와 같은 변환을 통해 포인터를 조작한다. 그리고 통일된 구조가 앞 부분에 존재해야 프로그램이 간단 하다.




그림 처럼 다른 각각의 struct는 앞 부분에 통일된 포인터 선언을 하고, 타입변환 하여 insettail 함수에 주면 이 함수에서는 next와 prev 변수만을 사용해 연결 한다. 여기에 연결된 모든 struct는 맨 앞에 2개의 연결 포인터 변수 next와 prev만 있으면 가능하다.
그런데 각각의 struct에서 next와 prev변수의 이름은 상관이 없다. 다음 처럼.
struct Member {
   struct Member *nextpoint;
   struct Member *pont;
    . . .
};
struct Customer {
   struct Customer *prev;
   struct Customer *next;
    . . .
};
이 두 가지 경우, 첫 번째는 포인터의 이름이 다른 경우이고 두 번째는 next와 prev가 바뀐 경우이다. 그러나  linked-list을 연결은 struct LinkedList에 의해 수행 되기 때문에 이 struct의 처음이 next이고 다음이 prev 이다. 따라서 Member와 Customer의 변수이름은 무시된다. 단지 2개의 포인터가 앞에 나오기만 하면 된다. 결과 타입변환이 sruct LinkedList에 의해 조작 된다.
그렇지만  struct Customer에서 다음의  struct을 찾기 위한 조작에서 혼돈을 일으킬 수 있으므로 이렇게는 사용을 하지 않는 것이 좋을 것이다.

그렀다면 다음과 같은 형태도 가능할 것이다.
struct Member {
   void *next;
   void *prev;
    . . .
};
struct Customer {
   void *next;
   void *prev;
    . . .
};

이것 역시 가능하다. 결국 타입변환이 되면 LinkedList로만 보일 뿐이다. 이 말은 결국 LinkedList의 struct와 함수에서만 조작하는 것이 좋다. C++ 같으면 class LinkedList 처럼 포인터 조작을 통한 연결.해제를 한 파일의 모듈에 모아 코딩하면 혼돈을 없애 일관성을 유지할 수 있다.

그렇다면 앞에 포인터 변수 2개가 나오지 않는다면

struct Member {
   void *next;
   void *prev;
    . . .
};

struct Customer {
   int type;
   void *next;
   void *prev;
    . . .
};

위의 2개의 struct로는 포인터를 연결할 수 없을까? 가능은 하나 복잡하다. 그리고 모듈화 과정에서 2개의 struct 구조를 모두 고려하여 복잡한 코딩이 필요하다. 가능은 하나 일관성 유지 등 모든 면에서 불가능하다고 생각해 버리는 것이 좋다.

그렇다면 2개의 포인터가 앞에만 나와야 하는가?

struct Member {
   int    size;
   struct Member *next;
   struct Member *prev;
    . . .
};
struct Customer {
   int    size;
   struct Customer *next;
   struct Customer *prev;
    . . .
};
이렇다면 일관성이 있다. 따라서 처음에 제시한 과정과 같다. 단지 포인터 위치가 한깐 밀렸을 뿐이다. 그렇다면
struct LinkedList{
   int    size;
   struct LinkedList*next;
   struct LinkedList*prev;
};

처럼 선언하여 타입변환 후에 포인터를 조작하면 모든 struct의 리스트 포인터가 같게 되어 일관성이 유지된다. 처음에 해당 struct의 크기나 타입을 넣어도 좋을 방법일 것이다.
inserttail함수도 그대로 사용 가능하다.

 struct Member *pmem = ( struct Member*) malloc( sizeof(struct Member) );
  pmem->size = sizeof(struct Member);
  inserttail( (LinkedList*) peme);
처럼 사용이 가능하다.

여기서도 포인터의 근본 원리인 변수의 주소값이 모든 타입에 같은 길이를 갖는다는 것과 포인터는 주소값을 저장하는 거라는 사실 다시 강조...

이와 같은 과정으로 예제 포르그램을 실행하면


여기서 2가지 struct가 linked-list의 통일된 앞부분 구조에 의해 실현 될 수 있다.
여기서 stype 변수는 각각이 어떤 구조체인가를 구별하기 위한 define 값이다. 아무래도 struct 안에 struct 구조 구별 숫자를 넣으면 다양한 코드가 가능하다.

이와 같은 구조는 리눅스 커널 프로그램을 짜다 보면 쉽게 보는 것이다. 특히 통신과 관련 된 리눅스의 커널속 sock 프로그램은 대표적 예이다.

Linked-list의 포인터의 다른 구성

struct LinkedList{
   struct LinkedList*next;
   struct LinkedList*prev;
};

struct Member {
   struct LinkedList list;
   int stype;
    . . .
};
struct Customer {
   struct LinkedList list;
   int stype;
    . . .
};

위와 같은 구조를 생각해 보면 구조적으로 처음 제시한 방법과 다른 것이 없다.
처음에 2개의 변수 next와 prev가 오고 나머지 변수들이 온다.

 struct Member *pmem = ( struct Member*) malloc( sizeof(struct Member) );
 inserttail( &pmem->list);

처럼 해당 struct을 만들고 inserttail함수를 사용하여 된다.


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

댓글 없음:

댓글 쓰기