이중 링크드 리스트를 사용할 때 장점과 단점은 다음과 같다.
장점은,
1. 데이터의 삽입, 삭제가 빠르다.
단점은,
1. 데이터의 추가 시 메모리 할당이 발생하고, 삭제 시 메모리 해제가 발생한다.
2. 메모리 조각이 많이 발생한다.
3. 항상 처음부터 검색해야 한다.
이 중 단점 1,2를 배열을 사용해서 해결할 수 있다. 배열을 이용하면 메모리의 할당 및 해제가 자주 발생하지
않기 때문에 메모리 단편화를 줄일 수 있어, 효율적인 프로그램을 개발할 수 있다. 또한 메모리를 할당할 때
마다 발생하는 페이지 할당이 줄기 때문에 적은 메모리를 사용하게 된다. 이제는 링크드 리스트를 사용할 때
아래의 코드와 같이 배열을 사용하여 만들어 보자.
다음은 소스 코드이다.
#include <stdio.h> // printf, scanf
#include <stdlib.h> // malloc
#include <malloc.h> // malloc
#include <memory.h> // memset
struct tag_node
{
int data; // data
struct tag_node* next; // 4바이트 구조체 포인터
struct tag_node* prev; // 4바이트 구조체 포인터
};
typedef struct tag_node NODE;
typedef struct tag_node* PNODE;
struct tag_block
{
struct tag_block* block;
struct tag_block* next;
};
typedef struct tag_block BLOCK;
typedef struct tag_block* PBLOCK;
const int block_size = 100; // 배열의 크기
PNODE NewNode( PNODE prev, PNODE next );
void AddTail( int data );
void AddHead( int data );
int RemoveNode( PNODE pnode );
void PRINT();
void FREE();
PNODE HeadNode = NULL, TailNode = NULL, FreeNode = NULL;
PBLOCK NodeBlock = NULL;
void main( void )
{
int data = 1;
while( data )
{
scanf( "%d", &data );
AddTail( data );
}
PRINT();
FREE();
}
PNODE NewNode( PNODE PrevNode, PNODE NextNode )
{
PNODE Node;
if( !FreeNode )
{
int i;
PNODE pnode; // 1차원 배열의 포인터
PBLOCK pblock;
// BLOCK + block_size 만큼 1차원 배열을 할당
// 메모리 구조는 다음과 같이 한 개의 BLOCK 구조체와
// 여러 개의 NODE 구조체로 구성된다.
// | BLOCK(8바이트) | NODE * block_size |
pblock = (PBLOCK)malloc( sizeof(BLOCK) + sizeof(NODE) * block_size );
// BLOCK의 첫 8바이트를 가지고 NODE 블럭을 관리한다.
if( !NodeBlock )
{
NodeBlock = pblock;
NodeBlock->block = pblock;
NodeBlock->next = NULL;
}
else
{
pblock->next = NodeBlock;
NodeBlock = pblock;
NodeBlock->block = pblock;
}
// FreeNode를 BLOCK의 크기인 8만큼 더한 위치로 설정
FreeNode = (PNODE)(pblock + 1);
memset( FreeNode, 0, sizeof(NODE) * block_size );
pnode = FreeNode;
// 각 배열 요소의 next를 그 다음 배열 요소로 설정
// 맨 끝의 배열 요소는 memset 함수에 의해 자동으로 NULL이 됨
for( i=0; i<block_size-1; i++ )
{
pnode[i].next = &pnode[i+1];
}
}
Node = FreeNode;
FreeNode = FreeNode->next; // 다음 Node로 이동
Node->prev = PrevNode; // 이전 노드 설정
Node->next = NextNode; // 다음 노드 설정
return Node;
}
void AddTail( int data )
{
PNODE pnode;
pnode = NewNode( TailNode, NULL );
pnode->data = data;
if( !HeadNode )
{
HeadNode = TailNode = pnode;
return;
}
TailNode->next = pnode;
pnode->prev = TailNode;
TailNode = pnode;
}
void AddHead( int data )
{
PNODE pnode;
pnode = NewNode( TailNode, NULL );
pnode->data = data;
if( !HeadNode )
{
HeadNode = TailNode = pnode;
return;
}
HeadNode->prev = pnode;
pnode->next = HeadNode;
HeadNode = pnode;
}
int RemoveNode( PNODE pnode )
{
PNODE PrevNode;
PNODE NextNode;
PrevNode = pnode->prev;
NextNode = pnode->next;
// 제거되는 노드가 HeadNode인 경우
if( pnode == HeadNode )
{
HeadNode = HeadNode->next;
}
// 제거되는 노드가 TailNode인 경우
if( pnode == TailNode )
{
TailNode = TailNode->prev;
}
// 제거되는 노드의 이전 노드가 있는 경우
if( PrevNode )
{
PrevNode->next = NextNode;
}
// 제거되는 노드의 다음 노드가 있는 경우
if( NextNode )
{
NextNode->prev = PrevNode;
}
// FreeNode의 맨 앞으로 연결
pnode->next = FreeNode;
FreeNode = pnode;
return pnode->data;
}
void PRINT()
{
PNODE pnode = HeadNode; // 맨 앞의 링크드 리스트 위치로 설정
while( pnode )
{
printf( "%d ", pnode->data );
pnode = pnode->next; // 다음 리스트의 번지값으로 pnode의 값을 변경
}
}
void FREE()
{
PBLOCK pblock;
pblock = NodeBlock;
while( pblock )
{
NodeBlock = pblock->next;
free( pblock );
pblock = NodeBlock;
}
}
위 코드에서 NewNode()라는 함수는 아래의 그림과 같이 BLOCK 구조체와 NODE 구조체를 생성하는 함수이다.
이 함수는 내부적으로 malloc() 함수에 의해 BLOCK 구조체 한 개, NODE 구조체 100개를 생성한다.
NODE가 위와 같이 한 번에 100개(또는 1000개도 가능) 생성되면, 100개의 NODE를 다 사용할 때까지
더는 메모리 할당이 발생하지 않는다. 이 것은 메모리 단편화를 없앨 수 있는 방법이기도 하다. 만약
NODE를 필요할 때마다 할당한다면 무수히 많은 메모리 조각이 생길 것이고, 결국 작은 메모리 조각을
할당하고 관리하기 위해 좀 더 많은 메모리가 필요해 진다.
'프로그래밍 > C' 카테고리의 다른 글
linux kernel에서 쓰이는 자료구조.(double linked list and tree) (0) | 2013.04.19 |
---|---|
포인터? 포인터변수? 포인터의 이해 (0) | 2013.04.19 |
함수 포인터 (0) | 2013.04.19 |
linked list [펌] (0) | 2013.04.19 |
Double linked list (0) | 2013.04.19 |