'프로그래밍/C'에 해당되는 글 14건

  1. 2013.04.19 함수 포인터
  2. 2013.04.19 linked list [펌]
  3. 2013.04.19 Double linked list
  4. 2013.03.15 C언어를 굳이 배워야하는이유[펌]
프로그래밍/C 2013. 4. 19. 00:05

모든 포인터는 메모리 번지를 기억하기 위한 것이다. 함수 또한 메모리의 특정 공간에 존재하며,

그런 이유로 함수 포인터를 사용해서 특정 함수의 시작 번지를 기억할 수 있다.

 

함수 포인터는 함수의 원형(prototype)과 유사하다. 다음과 같이 함수 원형이 있을 때

 

    int  my_func( int );

 

함수 포인터는 다음과 같이 선언한다.

 

    int  (*PF) ( int );

 

둘의 상관 관계를 보면,  my_func이 (*PF)로 바뀐 것을 확인할 수 있으며, 여기서 PF가 함수 포인터

변수의 이름이 된다. PF가 변수 이름이므로 다른 이름으로 변경해도 된다. 아래는 왼쪽이 함수의 선언

오른쪽이 그 함수에 대한 함수 포인터의 선언이다.

 

void  f ( void );                -> void  (*pf) ( void );

int    f ( void );                 -> int  (*pf) (void );

double  f( double );          -> double (*pf) (void);

double  f( double, float );  -> double  (*pf) (double, float );

...

 

함수 포인터는 그 길이가 매우 길어질 수 있기 때문에 다음과 같이 재정의해서 사요하는 것이 좋다.

 

    typedef  int  (*MyFunc) ( int );

 

typedef은 int (*) (int)형 포인터를 MyFunc라고 재정의한다. 이렇게 정의되면,

 

    int  (*PF) ( int );  

 

 

    MyFunc   PF;

 

는 동일한 선언이 된다. 특히 함수 포인터를 리턴값으로 사용해야 하는 경우는 반드시 typedef에 의해

재정의한 후 사용해야 한다.

 

    typedef  int  (*MyFunc) ( int );

 

    int f1( int )

    {

        return  0;

    }

 

    MyFunc   func( void )  

    // int (*)(int) func( void )  사용할 수 없음

    {

        return F1;

    }

 

다음은 함수 포인터를 리턴하는 간단한 예이다.

 

#include <stdio.h>

typedef    int (*Func)(int);       // int (*)(int) 형 포인터를 Func라고 재정의

int  my_func( int a )
{
    printf( "%d \n", a );
    return  0;
}

void my_func2( int(*myF)(int) )       // 함수의 매개 변수로 전달
{
    myF( 2 );
}

void my_func3( Func myF )           // my_func2와 동일
{
    myF( 3 );
}

Func get_func( void )
{
    return  my_func;        //  my_func함수 포인터를 리턴
}

void main( void )
{
    Func  myF;           // int  (*myF) (int); 와 동일, myF는 함수 포인터 변수
    myF = get_func();  // 함수 포인터를 리턴 받음
    myF( 1 );              // 함수 포인터 변수 myF를 사용하여 my_func 함수 호출, my_func(3)과 동일
    my_func2( my_func );     // my_func 함수를 매개 변수로 전달
    my_func3( my_func );     // my_func 함수를 매개 변수로 전달
}


출처 : http://blog.naver.com/PostList.nhn?blogId=eunchol71 

'프로그래밍 > C' 카테고리의 다른 글

포인터? 포인터변수? 포인터의 이해  (0) 2013.04.19
Double linked list with using array  (0) 2013.04.19
linked list [펌]  (0) 2013.04.19
Double linked list  (0) 2013.04.19
C언어를 굳이 배워야하는이유[펌]  (0) 2013.03.15
//
프로그래밍/C 2013. 4. 19. 00:04

링크드 리스트(linked list)는 뜻 그대로 연결 리스트를 의미한다. C 언어에서 링크드 리스트는 구조체를

연결하기 위해 사용되며, 아래와 같이 여러 개의 구조체가 있을 때, next 포인터는 다음 구조체의 번지를

갖게 된다. 여기서 head는 항상 맨 앞의 구조체 번지를 갖고 있고, tail은 맨 뒤의 구조체 번지를 갖고 있다.

head 포인터를 통해서 맨 처음 구조체의 번지를 구한 후, 그 구조체 안의 next 포인터를 통해서 그 다음에

연결되어 있는 구조체의 번지를 구할 수 있다. 포인터가 번지값을 갖을 수 있기 때문에 아래와 같은 링크드

리스트라은 것이 가능하다.

 

 

 

 

다음은 위 그림에 대한 간단한 예이다. int*는 struct linked_list*를 사용해야 하는데, 여기서는 예를 들기 위해

int*를 사용하였다.

 

    struct linked_list

    {

        int data;

        int* next;

    };

 

    struct linked_list  list[3];

    int* head, *tail;

    head = &list[0];

    tail = &list[2];

    list[0]->next = &list[1]; // 첫 번째 구조체의 next 포인터는 두 번째 구조체(list[1])의 번지값을 갖는다.

    list[1]->next = &list[2]; // 두 번째 구조체의 next 포인터는 세 번째 구조체(list[2])의 번지값을 갖는다.

    list[2]->next = NULL;     // 마지막 리스트는 항상 NULL이 되어야, 링크드 리스트의 끝이 어디인지 판단할 수 있음

   

    int* p = head;                 // p는 첫 번째 구조체의 번지

    int* next = p->next ;      // next는 두 번째 구조체의 번지

    int* next2 = next->next; // next2는 세 번째 구조체의 번지

 

    p->data;                        // 첫 번째 구조체의 data 값, list[0].data와 동일

    next->data;                   // 두 번째 구조체의 data 값, list[1].data와 동일

    next2->data;                 // 세 번째 구조체의 data 값, list[2].data와 동일

 

--------------------------------------------&^.^-----------------------------------------------

 

#include <stdio.h>       // printf, scanf
#include <stdlib.h>      // malloc
#include <malloc.h>     // malloc
#include <memory.h>   // memset

 

// 구조체 포인터 변수인 next는 다음 구조체의 번지값을 기억하기 위해 사용된다. next가 다음 구조체의

// 번지값을 기억하고 있기 때문에 next를 통해서 구조체를 연결할 수 있다.

struct tag_list
{
    int data;              // data
    struct tag_list* next;     // 4바이트 구조체 포인터
};

 

// struct tag_list를 좀 더 짧게 재정의한다.

typedef struct tag_list  LIST;           // struct tag_list 대신에 LIST를 사용할 수 있도록 정의
typedef struct tag_list*  PLIST;       // struct tag_list* 대신에 PLIST를 사용할 수 있도록 정의

 

void Add( PLIST plist );
void PRINT();

void FREE();

// LIST가 순차적으로 여러 개 있을 때, 맨 앞에 있는 LIST의 번지를 head 포인터가 저장하고 있으며,

// 맨 뒤에 있는 구조체의 번지는 tail이 저장한다. head 포인터는 항상 LIST의 선두를 가리키고 있기

// 때문에, head에서부터 구조체를 연속해서 찾아갈 수 있다.

PLIST  head = NULL, tail = NULL;     // 전역 변수, 맨 앞, 맨 뒤의 구조체 번지를 저장

 

void main( void )
{
    int data = 1;
    
    while( data )
    {
        PLIST plist;
        scanf"%d"&data );

        plist = (PLIST)mallocsizeof(LIST) );  // 메모리를 8바이트만큼 동적 할당(생성)
        memset( plist, 0, sizeof(LIST) );         // 할당 받은 메모리를 0으로 초기화

        plist->data = data;

        Add( plist );
    }

    PRINT();
}

 

// 링크드 리스트 생성

void Add( PLIST plist )
{
    plist->next = NULL;    // 새로 생성되는 것은 링크드 리스트의 맨 뒤에 더해지기 때문에 항상 NULL로 초기화 함.

    if( !head )                 // head 포인터가 아직 아무 것도 가리키지 않으면, if( head == NULL )과 동일
    {
        head = tail = plist;
        return;
    }

    tail->next = plist;
    tail = plist;
}

 

void PRINT()
{
    PLIST plist = head;         // 맨 앞의 링크드 리스트 위치로 설정

    while( plist )                  // while( plist != NULL )
    {
        printf( "%d \n", plist->data );
        plist = plist->next;    // 다음 리스트의 번지값으로 plist의 값을 변경
    }
}


void FREE()
{
    PLIST plist = head;

    while( plist )
    {
        head = plist->next;
        free( plist );           // 할당한 메모리 해제
        plist = head;
    }

 

    head = tail = NULL;      // 포인터 초기화, 재 사용 시 필요
}

 

--------------------------------------------&^.^-----------------------------------------------

 

malloc() 함수

 

메모리를 할당하는 malloc 함수는 다음과 같이 사용할 수 있다. malloc() 함수는 void*형을 반환하기 때문에

데이터형을 일치시켜야 한다. 왜 void*형을 반환하는가? 만약 int*형을 반환한다면 int*형만 사용하는 것이고

char*형을 반환한다면, char*형만 사용하는 것이 될 것이다. 그래서 모든 포인터에서 다 사용할 수 있도록

malloc() 함수는 void*형을 리턴한다.

 

    int* pi = (int*)malloc( 4 );     // 4바이트 할당

    ...

    free( pi );                         // 할당된 메모리 해제

 

    char* p = malloc( 100 );        // 100 바이트 할당

    ...

    free( p );

 

이런 원리를 이용해서 다음과 같이 LIST 구조체의 메모리를 할당한다.

 

    plist = (PLIST)mallocsizeof(LIST) );  // 메모리를 8바이트만큼 동적 할당(생성)
    memset( plist, 0, sizeof(LIST) );         // 할당 받은 메모리를 0으로 초기화

 

malloc() 함수는 메모리 할당 후 그 영역을 초기화하지 않고 돌려 준다. 그러므로 항상 memset() 함수를

사용해서 초기화 해 주는 습관을 갖는 것이 좋다. 위 예제에서는 반드시 초기화할 필요는 없다. 초기화하지

않아도 다른 값으로 초기화되기 때문이다.

 

링크드 리스트 생성 및 연결

 

// 링크드 리스트 생성

void Add( PLIST plist )
{
    plist->next = NULL;

    if( !head )

    {
        head = tail = plist;
        return;
    }

    tail->next = plist;
    tail = plist;
}

 

위 코드를 한 줄씩 분석해 보자.

 

    plist->next = NULL;

 

plist는 동적으로 할당된 메모리의 번지의 값을 저장하고 있는 포인터이다. 링크드 리스트를 생성해서

맨 뒤에 붙일 것이기 때문에 NULL로 초기화를 한다. 링크드 리스트는 맨 뒤의 구조체의 next는 항상

NULL로 끝나야 한다.

 

    if( !head )

    {
        head = tail = plist;

        return;
    }

 

head가 NULL이면, 아직 링크드 리스트가 하나도 없는 상태이다. 이 코드는 링크드 리스트에서

단 한 번만 실행된다. head와 tail은 맨 처음 생성된 구조체의 포인터 값으로 초기화 된다.

 

    tail->next = plist;

 

이 코드는 두 번째 구조체부터 적용된다. 첫 번째 구조체는 위의 코드에서 if( !head )에 의해 head와

tail을 초기화한 후 리턴한다. tail은 첫 번째 구조체의 번지를 갖고 있기 때문에 첫 번째 구조체의 next

에 지금 추가할 구조체의 번지값을 대입한다. 그리고 나서 다음과 같이 tail을 추가할 구조체의 번지로

재 설정한다. 그러면 head와 tail의 값은 서로 틀려지고, tail은 두 번째(또는 마지막) 구조체의 번지를

갖게 된다. 

 

    tail = plist;

 

다시 정리해 보면, 첫 번째 호출에서는 다음 코드만 실행된다.

 

void Add( PLIST plist )
{
    plist->next = NULL;

    if( !head )

    {
        head = tail = plist;
        return;
    }

}

 

두 번째를 포함한 이후의 호출에서는 다음 코드만 실행된다.

 

void Add( PLIST plist )
{
    plist->next = NULL;

    tail->next = plist;
    tail = plist;
}

 

코드에서 보듯이 tail은 계속 맨 뒤에 붙여지는 포인터의 값을 저장하고 있다. 그래서 tail 포인터의

값을 통해 항상 맨 뒤의 구조체에 접근할 수 있게 되며, head 포인터를 통해서 맨 앞의 구조체에

접근할 수 있게 된다.

 

 

링크드 리스트로 연결된 구조체를 맨 앞부터 맨 뒤까지 접근

 

 

void PRINT()
{
    PLIST plist = head;         // 맨 앞의 링크드 리스트 위치로 설정

    while( plist )
    {
        printf(
 "%d \n", plist->data );
        plist = plist->next;    // 다음 리스트의 번지값으로 plist의 값을 변경
    }
}

 

링크드 리스트는 항상 맨 앞부터 접근하기 때문에 다음과 같이 맨 앞의 구조체 번지를 갖고 있는

head 포인터의 값을 얻어 온다.

 

    PLIST plist = head;

 

그런 후 plist가 NULL이 아닌 동안 링크드 리스트를 순회하면서 다음과 같이 값을 출력한다.

 

    printf( "%d \n", plist->data );

 

다음 문장은 링크드 리스트를 이해하는 핵심이므로 주의해서 생각해야 한다.

 

    plist = plist->next;        // 두 번째, 세 번째, 네 번째, ... n 번째 링크드 리스트의 번지를 가져옴.

 

맨 처음에 plist는 구조체의 선두(head) 번지값을 갖고 있다. 그리고 위의 코드에 의해 다음 구조체의

번지를 얻어 온다. 이제는 plist는 더 이상 첫 번째 구조체를 가리키지 않고, 두 번째 구조체를 가리키게

된다. plist가 두 번째 구조체를 가리키기 때문에 plist 구조체 포인터를 통해서 두 번째 구조체의 data 값과

next 포인터를 접근할 수 있다. 만약 구조체가 위의 그림과 같이 1000번지, 2000번지, 3000번지에 저장되어

있다면, head의 값은 1000이고, plist의 값도 1000으로 초기화 된다. 그리고 나서 plist = plist->next에 의해

plist는 2000으로 값이 변경된다. 2000은 두 번째 구조체의 번지이므로 plist를 통해 두 번째 구조체를 마음

대로 접근해서 사용할 수 있다. 만약 더 이상 링크드 리스트가 존재하지 않으면 plist는 NULL이 된다. 그러면

while( plist )에 의해 while문은 종료된다.

 

 

링크드 리스트의 메모리 해제

 

 

void FREE()
{
    PLIST plist = head;

    while( plist )
    {
        head = plist->next;
        free( plist );           // 할당한 메모리 해제
        plist = head;
    }

 

    head = tail = NULL;      // 포인터 초기화, 재 사용 시 필요
}

 

malloc() 함수에 의해 할당된 메모리는 반드시 free() 함수에 의해 해제 되어야 한다. 만약 메모리를

할당한 후 해제하지 않는다면, 언젠가는 메모리가 부족해질 것이다. 그러므로 malloc()에 의해 할당된

메모리는 free() 함수로 해제하도록 하자.

 

    PLIST plist = head;

 

우선 맨 처음의 구조체 번지를 갖고 있는 head 포인터로 초기화한다.

 

    while( plist )
    {
        ...   

    }

 

plist가 NULL이 아닌 동안 메모리를 루프를 순환한다.

 

    head = plist->next;

 

head 구조체 포인터는 더 이상 필요하지 않기 때문에 다음 구조체의 번지를 임시 저장한다.


    free( plist );           // 할당한 메모리 해제
  

plist가 가리키는 번지부터 메모리 할당을 해제한다.

 

    plist = head;

 

plist에 다음 구조체 번지를 대입한다. head에는 조금 전에 다음 구조체의 번지를 임시로

저장해 두었다. 이렇게 끝까지( plist->next가 NULL일 때까지) 링크드 리스트를 순회화면서

malloc() 함수에 의해 할당 받은 메모리를 모두 해제한다.


 

    head = tail = NULL;      // 포인터 초기화, 재 사용 시 필요

 

모든 링크드 리스트가 해제 되었기 때문에 head와 tail은 NULL로 초기화한다.


출처 : http://blog.naver.com/PostList.nhn?blogId=eunchol71 

'프로그래밍 > C' 카테고리의 다른 글

포인터? 포인터변수? 포인터의 이해  (0) 2013.04.19
Double linked list with using array  (0) 2013.04.19
함수 포인터  (0) 2013.04.19
Double linked list  (0) 2013.04.19
C언어를 굳이 배워야하는이유[펌]  (0) 2013.03.15
//
프로그래밍/C 2013. 4. 19. 00:03

이중 링크드 리스트는 단일 링크드 리스트에 이전 구조체의 번지값을 저장할 수 있는 포인터가 하나 더

추가되는 것이다. 다음 그림을 보며, next외에 prev라는 포인터가 하나 더 존재하며, prev 포인터는 이전
구조체의 메모리 번지를 저장하는 것을 확인할 수 있다.
 
 

 

 

 

다음은 위 그림에 대한 간단한 예이다.

 

    struct linked_list

    {

        int data;

        int* next;

    };

 

    struct linked_list  list[3];

    struct linked_list* head, *tail;

    head = &list[0];

 

    list[0]->next = &list[1]; // 첫 번째 구조체의 next 포인터는 두 번째 구조체(list[1])의 번지값을 갖는다.

    list[1]->next = &list[2]; // 두 번째 구조체의 next 포인터는 세 번째 구조체(list[2])의 번지값을 갖는다.

    list[2]->next = NULL;     // 마지막 리스트는 항상 NULL이 되어야, 링크드 리스트의 끝이 어디인지 판단할 수 있음   

 

    list[0]->prev = NULL;    // 첫 번째 구조체의 prev는 항상 NULL이 되어야, 링크드 리스트의 처음이 어디인지 알 수 있음

    list[1]->prev = &list[0]; // 두 번째 구조체의 prev 포인터는 첫 번째 구조체(list[0])의 번지값을 갖는다.

    list[2]->prev = &list[1];  // 세 번째 구조체의 prev 포인터는 두 번째 구조체(list[1])의 번지값을 갖는다.

   

    struct linked_list* p = head;                 // p는 첫 번째 구조체의 번지

    struct linked_list* next = p->next ;      // next는 두 번째 구조체의 번지

    struct linked_list* next2 = next->next; // next2는 세 번째 구조체의 번지      

 

    p->data;                        // 첫 번째 구조체의 data 값, list[0].data와 동일

    next->data;                   // 두 번째 구조체의 data 값, list[1].data와 동일

    next2->data;                 // 세 번째 구조체의 data 값, list[2].data와 동일

 

    struct linked_list* p = tail;                 // p는 마지막 구조체의 번지

    struct linked_list* prev = p->prev;    // prev는 두 번째 구조체의 번지

    struct linked_list* prev2 = prev->prev;    // prev2는 첫 번째 구조체의 번지

 

    p->data;                        // 세 번째 구조체의 data 값, list[2].data와 동일

    prev->data;                   // 두 번째 구조체의 data 값, list[1].data와 동일

    prev2->data;                 // 첫 번째 구조체의 data 값, list[0].data와 동일

 

--------------------------------------------&^.^-----------------------------------------------

 

#include <stdio.h>       // printf, scanf
#include <stdlib.h>      // malloc
#include <malloc.h>     // malloc
#include <memory.h>   // memset

 

// 구조체 포인터 변수인 next는 다음 구조체의 번지값을 기억하기 위해 사용된다. next가 다음 구조체의

// 번지값을 기억하고 있기 때문에 next를 통해서 구조체를 연결할 수 있다.

struct tag_list
{
    int data;              // data
    struct tag_list* next;     // 4바이트 구조체 포인터
    struct tag_list* prev;     // 4바이트 구조체 포인터
};

 

// struct tag_list를 좀 더 짧게 재정의한다.

typedef struct tag_list  LIST;           // struct tag_list 대신에 LIST를 사용할 수 있도록 정의
typedef struct tag_list*  PLIST;       // struct tag_list* 대신에 PLIST를 사용할 수 있도록 정의

 

void AddTail( PLIST plist );
void AddHead( PLIST plist );
void 
PRINT();

void FREE();

// LIST가 순차적으로 여러 개 있을 때, 맨 앞에 있는 LIST의 번지를 head 포인터가 저장하고 있으며,

// 맨 뒤에 있는 구조체의 번지는 tail이 저장한다. head 포인터는 항상 LIST의 선두를 가리키고 있기

// 때문에, head에서부터 구조체를 연속해서 찾아갈 수 있다.

PLIST  head = NULL, tail = NULL;     // 전역 변수, 맨 앞, 맨 뒤의 구조체 번지를 저장

 

void main( void )
{
    int data = 1;
    
    while( data )
    {
        PLIST plist;
        scanf"%d"&data );

        plist = (PLIST)mallocsizeof(LIST) );  // 메모리를 12바이트만큼 동적 할당(생성)
        memset( plist, 0, sizeof(LIST) );         // 할당 받은 메모리를 0으로 초기화

        plist->data = data;

        AddTail( plist );
    }

    PRINT();
}

 

// 링크드 리스트를 뒤로 추가

void AddTail( PLIST plist )
{
    plist->next = NULL;    // NULL로 초기화

    plist->prev = NULL;    // NULL로 초기화

   

    if( !head )                 // head 포인터가 아직 아무 것도 가리키지 않으면, if( head == NULL )과 동일
    {
        head = tail = plist;
        return;
    }

    tail->next = plist;       // 다음(next) 포인터 설정

    plist->prev = tail;       // 이전(prev) 포인터 설정
    tail = plist;
}

 

// 링크드 리스트를 앞으로 추가

void AddHead( PLIST plist )
{
    plist->next = NULL;
    plist->prev = NULL;

 

    if( !head )
    {
        head = tail = plist;
        return;
    }

 

    head->prev = plist;   // 이전(prev) 포인터 설정
    plist->next = head;   // 다음(next) 포인터 설정
    head = plist;
}

 

void PRINT()
{
    // 맨 앞의 구조체부터 맨 뒤의 구조체로 순회

    PLIST plist = head;         // 맨 앞의 링크드 리스트 위치로 설정

    while( plist )                  // while( plist != NULL )
    {
        printf( "%d \n", plist->data );
        plist = plist->next;    // 다음 리스트의 번지값으로 plist의 값을 변경
    }

    // 맨 뒤의 구조체부터 맨 앞의 구조체로 순회

    PLIST plist = tail;            // 맨 뒤의 링크드 리스트 위치로 설정

    while( plist )                  // while( plist != NULL )
    {
        printf( "%d \n", plist->data );
        plist = plist->prev;     // 이전 리스트의 번지값으로 plist의 값을 변경
    }
}


void FREE()
{
    PLIST plist = head;

    while( plist )
    {
        head = plist->next;
        free( plist );           // 할당한 메모리 해제
        plist = head;
    }

 

    head = tail = NULL;      // 포인터 초기화, 재 사용 시 필요
}

 

--------------------------------------------&^.^-----------------------------------------------

 

아래의 내용은 단일 링크드 리스트와 유사합니다.

 

malloc() 함수

 

메모리를 할당하는 malloc 함수는 다음과 같이 사용할 수 있다. malloc() 함수는 void*형을 반환하기 때문에

데이터형을 일치시켜야 한다. 왜 void*형을 반환하는가? 만약 int*형을 반환한다면 int*형만 사용하는 것이고

char*형을 반환한다면, char*형만 사용하는 것이 될 것이다. 그래서 모든 포인터에서 다 사용할 수 있도록

malloc() 함수는 void*형을 리턴한다.

 

    int* pi = (int*)malloc( 4 );     // 4바이트 할당

    ...

    free( pi );                         // 할당된 메모리 해제

 

    char* p = malloc( 100 );        // 100 바이트 할당

    ...

    free( p );

 

이런 원리를 이용해서 다음과 같이 LIST 구조체의 메모리를 할당한다.

 

    plist = (PLIST)mallocsizeof(LIST) );  // 메모리를 12바이트만큼 동적 할당(생성)
    memset( plist, 0, sizeof(LIST) );         // 할당 받은 메모리를 0으로 초기화

 

malloc() 함수는 메모리 할당 후 그 영역을 초기화하지 않고 돌려 준다. 그러므로 항상 memset() 함수를

사용해서 초기화 해 주는 습관을 갖는 것이 좋다. 위 예제에서는 반드시 초기화할 필요는 없다. 초기화하지

않아도 다른 값으로 초기화되기 때문이다.

 

링크드 리스트 생성 및 연결

 

// 링크드 리스트 생성

void AddTail( PLIST plist )

{

    plist->next = NULL;    // NULL로 초기화

   plist->prev = NULL;    // NULL로 초기화

   

    if( !head )                 // head 포인터가 아직 아무 것도 가리키지 않으면, if( head == NULL )과 동일
    {
        head = tail = plist;
        return;
    }

 

    tail->next = plist;       // 다음(next) 포인터 설정

   plist->prev = tail;       // 이전(prev) 포인터 설정
    tail = plist;

}

 

위 코드는 단일 링크드 리스트와 거의 유사하며, 보라색 문장이 새로 추가되었다.

 

   plist->prev = tail;       // 이전(prev) 포인터 설정

이 코드는 두 번째 구조체부터 적용된다. 첫 번째 구조체는 위의 코드에서 if( !head )에 의해 head와

tail을 초기화한 후 리턴한다. tail은 첫 번째 구조체의 번지를 갖고 있기 때문에 첫 번째 구조체의 next

에 지금 추가할 구조체의 번지값을 대입한다. 그리고 나서 다음과 같이 tail을 추가할 구조체의 번지로

재 설정한다. 그러면 head와 tail의 값은 서로 틀려지고, tail은 두 번째(또는 마지막) 구조체의 번지를

갖게 된다. 여기서 tail에 추가되는 구조체는 이전 구조체의 번지를 알아야 하기 때문에 현재 생성된

구조체의 prev에 tail 포인터의 값을 먼저 대입한다. 그리고 나서 tail 포인터의 값을 다음과 같이 변경

해 준다.

 

    tail = plist;

 

 

링크드 리스트로 연결된 구조체를 맨 앞부터 맨 뒤까지 접근

 

 

void PRINT()
{
    // 맨 앞의 구조체부터 맨 뒤의 구조체로 순회

    PLIST plist = head;         // 맨 앞의 링크드 리스트 위치로 설정

    while( plist )                  // while( plist != NULL )
    {
        printf( "%d \n", plist->data );
        plist = plist->next;    // 다음 리스트의 번지값으로 plist의 값을 변경
    }

    // 맨 뒤의 구조체부터 맨 앞의 구조체로 순회

    PLIST plist = tail;            // 맨 뒤의 링크드 리스트 위치로 설정

    while( plist )                  // while( plist != NULL )
    {
        printf( "%d \n", plist->data );
        plist = plist->prev;     // 이전 리스트의 번지값으로 plist의 값을 변경
    }
}

 

단일 링크드 리스트가 항상 맨 앞에서 접근하는 것이 비해, 이중 링크드 리스트는 맨 앞 또는

맨 뒤에서 접근이 가능하다. 맨 앞부터 접근하려면 head 포인터를 사용하고, 맨 뒤부터 접근하려면

tail 포인터를 사용한다. 여기서 주의해야 할 것은 맨 앞부터 순회할 때는

 

    plist = plist->next;

 

를 사용하고, 맨 뒤부터 순회할 때는

 

    plist = plist->prev;

 

를 사용한다는 것이다. 이 것은 실수하기 쉬우므로 주의하도록 하자.

 

 

링크드 리스트의 메모리 해제

 

 

void FREE()
{
    PLIST plist = head;

    while( plist )
    {
        head = plist->next;
        free( plist );           // 할당한 메모리 해제
        plist = head;
    }

 

    head = tail = NULL;      // 포인터 초기화, 재 사용 시 필요
}

 

malloc() 함수에 의해 할당된 메모리는 반드시 free() 함수에 의해 해제 되어야 한다. 만약 메모리를

할당한 후 해제하지 않는다면, 언젠가는 메모리가 부족해질 것이다. 그러므로 malloc()에 의해 할당된

메모리는 free() 함수로 해제하도록 하자.

 

    PLIST plist = head;

 

우선 맨 처음의 구조체 번지를 갖고 있는 head 포인터로 초기화한다.

 

    while( plist )
    {
        ...   

    }

 

plist가 NULL이 아닌 동안 메모리를 루프를 순환한다.

 

    head = plist->next;

 

head 구조체 포인터는 더 이상 필요하지 않기 때문에 다음 구조체의 번지를 임시 저장한다.


    free( plist );           // 할당한 메모리 해제
  

plist가 가리키는 번지부터 메모리 할당을 해제한다.

 

    plist = head;

 

plist에 다음 구조체 번지를 대입한다. head에는 조금 전에 다음 구조체의 번지를 임시로

저장해 두었다. 이렇게 끝까지( plist->next가 NULL일 때까지) 링크드 리스트를 순회화면서

malloc() 함수에 의해 할당 받은 메모리를 모두 해제한다.


 

    head = tail = NULL;      // 포인터 초기화, 재 사용 시 필요

 

모든 링크드 리스트가 해제 되었기 때문에 head와 tail은 NULL로 초기화한다.

 

 

--------------------------------------------&^.^-----------------------------------------------

 

다음은 그림으로 알아보는 이중 링크드 리스트의 모습이다.

head와 tail 구조체 포인터는 처음에 NULL로 초기화되어 있다.


여기에 구조체 하나를 추가하면, head와 tail은 첫 번째 구조체의 번지값을 갖게 되며,

구조체 안의 next와 prev는 각각 NULL로 초기화 된다.


 

위 구조체에 다른 구조체를 하나 추가하면, head는 그대로 있고, tail은 다음 구조체의 번지값으로 변경된다.

또한 위 구조체의 next는 다음 구조체의 번지값인 2000으로 변경된다. 새로 추가된 구조체의 next와 prev도

적절하게 변경되어야 하는데, next는 다음 구조체가 없으므로 NULL로, prev는 이전 구조체의 번지값인 1000을

갖게 된다. tail이 가리키는 구조체의 prev는 어느 구조체를 가리키고 있는가? 바로 이전 구조체를 가리키고

있다. 그러므로 두 개의 구조체가 이중으로 연결된 모습을 확인할 수 있다.

 

 

여기에 구조체를 하나 더 추가하면 다음과 같이 된다. tail 포인터는 마지막 구조체로 이동하고

2000번지에 있는 두 번째 구조체의 next는 3000번지로 변경된다. 그리고 새로 추가되는 3000번지의

구조체에 next는 다음 구조체가 없기 때문에 NULL로 설정된다. 또한 3000번지의 prev는 이전 구조체의

번지인 2000을 갖게 된다.



우리는 head나 tail을 통해서 모든 구조체를 차례대로 옮겨 가면서 그 값을 참조할 수 있는데, head를 통해서

첫 번째 구조체를 찾을 수 있고, 첫 번째 구조체의 next를 통해서 그 다음 구조체(두 번째 구조체)를 찾을 수

있다. 그리고 두 번째 구조체에 있는 next 포인터를 통해서 그 다음 구조체를 찾을 수 있다. 이렇게 구조체가

연속적으로 연결되어 있는 것을 링크드 리스트라 한다.

 

--------------------------------------------&^.^-----------------------------------------------

 

 

링크드 리스트에서 구조체 제거

 

 

링크드 리스트에서 특정한 구조체를 제거하는 그리 어렵지 않다. 구조체의 제거는 3가지로 구분할 수 있다.

 

 맨 앞의 구조체를 제거하는 경우

 맨 뒤의 구조체를 제거하는 경우

 중간의 구조체를 제거하는 경우

 

다음과 같이 링크드 리스트가 있다고 가정하자.

 

 

 

⊙ 맨 앞의 구조체를 제거하는 경우

 

void Remove( PLIST plist )
{
    if( !plist ) return;    // NULL 포인터이면 리턴
 
    if( plist == head )  // 맨 앞의 구조체
    {
        PLIST  pRemove;
        pRemove = head;       // head를 임시 저장, pRemove = plist; 도 가능
        head = plist->next;    // head를 다음 구조체를 가리키도록 설정
        head->prev = NULL;  // 이전 구조체 번지를 NULL로 설정
        free( pRemove );       // 메모리 해제
       
        if( head == NULL ) tail = NULL;  // 더 이상 구조체가 없으면 tail을 NULL로 초기화
    }
}

 

맨 앞의 구조체가 제거되면 1000번지는 메모리가 해제(free)되고, head는 2000번지를 가리키도록 설정된다.

또한 2000번지에 있는 prev는 더 이상 1000번지를 가리키면 안되므로 NULL로 설정한다.

 

 

 

⊙ 맨 뒤의  구조체를 제거하는 경우

 

void Remove( PLIST plist )
{
    if( !plist ) return;    // NULL 포인터이면 리턴
 
    if( plist == tail )      // 맨 뒤의 구조체
    {
        PLIST  pRemove;
        pRemove = tail;       // tail을 임시 저장, pRemove = plist; 도 가능
        tail = plist->prev;    // tail을 이전 구조체를 가리키도록 설정
        tail->next = NULL;  // 다음 구조체 번지를 NULL로 설정
        free( pRemove );       // 메모리 해제
       
        if( tail == NULL ) head = NULL;  // 더 이상 구조체가 없으면 head를 NULL로 초기화
    }
}

 

맨 뒤의 구조체가 제거되면 3000번지는 메모리가 해제(free)되고, tail은 2000번지를 가리키도록 설정된다.

또한 2000번지에 있는 next는 더 이상 3000번지를 가리키면 안되므로 NULL로 설정한다.

 

 

 

⊙ 중간의 구조체를 제거하는 경우

 

void Remove( PLIST plist )
{
    if( !plist ) return;    // NULL 포인터이면 리턴
 
    if( plist != head && plist != tail )      // 중간의 구조체
    {
        PLIST  pRemove;
        pRemove = plist;     // plist를 임시 저장
        if( plist->prev )

            plist->prev->next = plist->next;    // 이전 구조체(1000번지)의 next와 다음 구조체(3000번지)를 연결

        if( plist->next )

            plist->next->prev = plist->prev;    // 다음 구조체(3000번지)의 prev와 이전 구조체(1000번지)를 연결

        free( pRemove );       // 메모리 해제
       
        if( tail == NULL ) head = NULL;  // 더 이상 구조체가 없으면 head를 NULL로 초기화
    }
}

 

중간의 구조체가 제거되면 2000번지는 메모리가 해제(free)된다. 이 때 중요한 것은 링크드 리스트의 연결이 중간에

끊기지 않도록 이전 구조체와 다음 구조체간에 연결을 설정하는 것이다. 위의 코드에서 다음은

 

    if( plist->prev )                                  // 이전 구조체가 있는지 검사

        plist->prev->next = plist->next;    // 이전 구조체(1000번지)의 next와 다음 구조체(3000번지)를 연결

 

이전 구조체가 있을 경우, 이전 구조체의 next 포인터를 3000번지로 설정하는 것이다. 이 것을 풀어서 써 보면

다음과 같다.

 

    if( plist->prev ) 

    {

        PLIST pTemp;

        pTemp = plist->prev;

        pTemp->next = plist->next;

    }

 

또한 다음과 같이 제거되는 구조체 다음의 구조체에 대하여 prev를 설정해야 한다.

 

    if( plist->next )

        plist->next->prev = plist->prev;    // 다음 구조체(3000번지)의 prev와 이전 구조체(1000번지)를 연결

 

이 코드를 풀어서 써 보면 다음과 같다.

 

    if( plist->next ) 

    {

        PLIST pTemp;

        pTemp = plist->next;

        pTemp->prev = plist->prev;

    }

 

 

 

 

 

다음은 전체 소스 코드이다.

 

void Remove( PLIST plist )
{
    if( !plist ) return;    // NULL 포인터이면 리턴
 
    if( plist == head )  // 맨 앞의 구조체
    {
        PLIST  pRemove;
        pRemove = head;       // head를 임시 저장, pRemove = plist; 도 가능
        head = plist->next;    // head를 다음 구조체를 가리키도록 설정
        head->prev = NULL;  // 이전 구조체 번지를 NULL로 설정
        free( pRemove );       // 메모리 해제
       
        if( head == NULL ) tail = NULL;  // 더 이상 구조체가 없으면 tail을 NULL로 초기화

        return;

    }

 

    if( plist == tail )      // 맨 뒤의 구조체
    {
        PLIST  pRemove;
        pRemove = tail;       // tail을 임시 저장, pRemove = plist; 도 가능
        tail = plist->prev;    // tail을 이전 구조체를 가리키도록 설정
        tail->next = NULL;  // 다음 구조체 번지를 NULL로 설정
        free( pRemove );       // 메모리 해제
       
        if( tail == NULL ) head = NULL;  // 더 이상 구조체가 없으면 head를 NULL로 초기화
        return;

    }

    if( plist != head && plist != tail )      // 중간의 구조체 if() {}은 생략 가능
    {
        PLIST  pRemove;
        pRemove = plist;     // plist를 임시 저장
        if( plist->prev )

            plist->prev->next = plist->next;    // 이전 구조체(1000번지)의 next와 다음 구조체(3000번지)를 연결

        if( plist->next )

            plist->next->prev = plist->prev;    // 다음 구조체(3000번지)의 prev와 이전 구조체(1000번지)를 연결

        free( pRemove );       // 메모리 해제
       
        if( tail == NULL ) head = NULL;  // 더 이상 구조체가 없으면 head를 NULL로 초기화
    }

}

 


 

--------------------------------------------&^.^-----------------------------------------------

 

 

링크드 리스트의 중간에 구조체 추가

 

 

링크드 리스트의 끝( AddTail()함수 )과 맨 앞( AddHead() 함수 )에 구조체를 추가하는 것은 위의 예제에서

이미 알아 보았다. 링크드 리스트의 중간에 구조체를 추가하는 것은 그리 어려운 작업이 아니며, next와 prev를

잘 설정하면 된다.

 

다음과 같이 링크드 리스트가 있다고 가정하자.


 

 

위 그림의 head와 tail 사이에 새로운 구조체를 추가하려면, 1000번지의 next와 2000번지의 prev가 새로 생성

되는 구조체를 가리키도록 수정되어야 한다. head의 next는 새로 생성되는 3000번지의 값으로 변경이 되어야

하고, tail의 prev 또한 새로 생성되는 3000번지의 값으로 변경이 되어야 한다.

 

 

 

 

다음은 소스 코드이다.

 

void AddMiddle( PLIST plist, PLIST node )
{
    if( !plist ) return;      // NULL 포인터이면 리턴
    if( !node  ) return;    // node가 존재하지 않으면 리턴
 
    plist->prev = node;                   // 위 그림의 j

    plist->next = node->next;          // 위 그림의 k

    if( node->next )
        node->next->prev = plist;      // 위 그림의 l

    node->next = plist;                   // 위 그림의 m
}

'프로그래밍 > C' 카테고리의 다른 글

포인터? 포인터변수? 포인터의 이해  (0) 2013.04.19
Double linked list with using array  (0) 2013.04.19
함수 포인터  (0) 2013.04.19
linked list [펌]  (0) 2013.04.19
C언어를 굳이 배워야하는이유[펌]  (0) 2013.03.15
//
프로그래밍/C 2013. 3. 15. 09:40

써 몇 년이 지난 이야기다. 교육을 받던 분이 굳이 C언어를 배워야 할 필요가 있냐고 물어보셨다. 그에 대한 개인적인 생각을 적어보도록 하겠다.



1. 체계화된 프로그래밍 순서를 익힐 수 있다.

모든 프로그래밍은 기본적으로 연산에 필요한 데이터를 메모리에 적재(load)하고 일련의 계산 결과를 저장(store)하게 된다. 이 후에 결과 데이터를 특정 위치로 전송하거나 복제하기도 한다. 이런 기본적인 계산 단위가 논리적으로 분기되고 복합적으로 연결된 것이 바로 프로그래밍이 되겠다.


그러나 가장 중요한 연산의 기본 단위는 읽고 계산하고 쓰는 작업이라는 점은 변하지 않는다.


프로그래밍 언어의 발전은 바로 이 읽고 계산하고 쓰는 작업을 편리하게 하거나 빠르게 하는 목적으로 발전해왔다. 편리함을 목적으로 하는 인터프리터 언어나 자바와 같은 언어들은 언어적인 측면에서 위 작업들을 간소화시켜준다. 예를 들어 A라는 위치에 있는 메모리를 B라는 변수로 복사한다고 치자. 몇몇 인터프리터 언어는 간단하게 '=' 기호로 이를 가능하게 한다.


1# python같은 인터프리터는 다음과 같이 선언된다.
2# B에 A를 저장한다.
3B = A


위와 같이 인터프리터 언어는 한 줄로 메모리를 읽을 수 있다. 그러나 한 줄의 코드에는 많은 내용이 함축되어있다. 예를 들어 B 변수에 메모리가 할당되어있지 않다면 할당해주고, 변수의 길이를 체크하여 부족하다면 재할당해주는 것도 포함된다. 또한 변수의 타입이 숫자인지 문자인지 변환하는 것도 가능하다.


그러면 위와 똑같은 작업을 C언어로 한다고 치자.

먼저 B의 메모리의 할당 여부 및 크기를 확인해야 할 것이다. 만일 작다면 reallocation으로 overflow를 막아야 하기 때문이다. 이를 위해 realloc을 사용해야 하며, 메모리 크기를 내부 로직에서 관리해야만 한다.


그 다음은 뭘 또 해야 할까? 음... A의 타입이 문자열인지, 문자열로 표시되는 숫자인지 다양한 검사를 해야 하는 경우도 있다. 데이터 오류를 막기 위해 여러 복잡한 검사를 해야 할지도 모른다. 이를 위해 정규표현식(REGEX)이나 바이트 단위로 데이터를 읽어야 하는 일까지 생길 수 있다.


결국 "B=A"의 한 줄로 표현되는 간단한 인터프리터 코드가 C언어에서는 수십줄이 넘어가게 된다는 것이다. 이 과정에서 버그도 심심찮게 일어날 수 있다. 이렇듯 인터프리터는 복잡한 작업을 간단하게 해주는 장점이 있다.


하지만 인터프리터 언어는 장점외에 단점도 분명히 존재한다!!


장점은 위와 같이 편리한 코딩, 즉 생산성이 높다는 점은 확실하다. 단점은 그와 반대로 너무 편리해서 어려운 프로그래밍을 못하게 된다는 점이다. 인터프리터 언어와 같이 언어가 대신해주는 작업이 많을수록 프로그래머는 쉬운 것에 익숙해지고 성능을 높이기 위해서 해야 하는 메모리, 연산기법들에 대해 둔감해지게 된다.


좀 멋진 말로 포장하면 transparency 때문에 하부 구조를 제대로 이해하지 못하게 된다는 것이다.


왜 메모리의 데이터 타입이나 경계를 검사해야 하는지? 즉 오버플로우를 신경써야 하는지 생각하지 않아서 편리하겠지만 그와 반대로 제한적인 메모리와 자원으로 프로그래밍 하는 환경에 적응하지 못할 수 있다는 점이다


또한 인터프리터와 같은 언어는 데이터를 옮길 때마다 메모리의 검사와 재할당 등이 발생하는 경우가 많으므로 생각보다 오버헤드가 커지기 십상이다. 이 때문에 일정 성능 이상 올릴 수 없는 구조적 문제도 따라온다.


물론 고급 레벨의 프로그래밍이 전혀 필요하지 않은 프로그래머라면 방금 한 말은 무시해도 된다. 하지만 전문적으로 프로그래밍을 하려고 한다면 언젠가는 고급 레벨을 접근해야만 하고, 그것은 메모리, 하드웨어에 대한 지식이 필요하다는 것을 의미한다.



2. 왜 어려운 프로그래밍 언어를 알아야 하는가?

C언어는 어려운 프로그래밍 언어다. 물론 과거에 사용되던 기계어나 어셈블리어보다는 쉽겠지만 평균적으로는 어려운 언어다. 이에 비해 다른 쉬운 언어들은 빠르게 익힐 수 있지만 덜 고민하게 되고 추상화된 프로그래밍 모델로 인해 쉬운 인터페이스에 집착하는 프로그래밍 스타일을 만들게 된다.


하지만 추상화나 쉬운 인터페이스에만 병적으로 집착하면 성능은 뒷전으로 내팽개치게 되는 성향이 생길 수 있기 때문에 조심해서 접근해야 한다. 이런 말을 하면 성능 문제는 하드웨어의 발전으로 커버할 수 있다고 말하는 사람도 있지만 비용대비 성능으로 볼 때 단순히 하드웨어의 발전만으로 커버되지 않는다. (하드웨어의 발전이 더뎌진 것도 하나의 이유가 된다.[2])


이런 연유로 인해 추상화되지 않은 프로그래밍 언어, 사람보다 컴퓨터가 어떻게 작동하는지 알 수 있는 언어를 알아 둘 필요가 있는 법이다. 


물론 십수년이 지나면 이런 환경도 사라지고 새로운 패러다임이 나올 지도 모르겠지만, 아직까지 C언어가 쌓아올린 컴퓨팅 환경은 견고하고, 컴퓨팅 환경을 이해하는데 있어 C언어를 대체할 만한 것도 없기 때문에 C언어는 꼭 배워두면 좋은 언어라고 생각된다. (물론 C언어를 배운 뒤에 다른 언어를 배워두는 것은 강력 추천한다. 개인적으로 파이썬은 정말 매력적인 언어다.)

'프로그래밍 > C' 카테고리의 다른 글

포인터? 포인터변수? 포인터의 이해  (0) 2013.04.19
Double linked list with using array  (0) 2013.04.19
함수 포인터  (0) 2013.04.19
linked list [펌]  (0) 2013.04.19
Double linked list  (0) 2013.04.19
//