3 메모리 할당

GNU 체제는 프로그램 상에서 분명하게 통제하는 방식으로 메모리를 할당하는 몇 가지 방법을 제공한다. 그것들은 일반성과 유효성의 범위 내에서 변화한다.

malloc 함수는 일반적인 동적 메모리를 충분히 확보한다. 3.3 [제한 없는 할당] 참조.

다른 함수로는 obstack이 있는데, 이것은 malloc보다는 덜 일반적이지만 스택 할당에서는 훨씬 더 편리하고 유용하다. 3.4 [Obstacks] 참조.

함수 alloca는 저장소를 동적으로 할당하고 자동적으로 해제해 준다. 3.5 [자동적인 크기 변화] 참조.


3.1 동적 메모리 할당이란?

동적 메모리 할당이란 프로그램이 어떤 정보를 어디에 저장해놓고 실행될 것인가를 결정하는 기법이다. 동적 메모리는 당신이 작업하는 데이터가 메모리 블록을 몇 개나 필요로 하는지 또는 그 메모리들을 얼마동안 사용할 것인지에 따라 요구된다.

예를 들면, 당신이 입력파일에서 읽어들인 한 개의 라인을 저장하려면 하나의 블럭을 필요로 한다. 그런데, 한 개의 라인이 어느 정도의 길이로 되어야 하는지가 제한이 없으므로, 당신은 저장소를 동적으로 할당할 수밖에 없으며, 그것을 당신이 더 큰 라인을 읽어들였을 때보다도 더 크게 할당해야만 한다. 또는, 당신이 입력 데이터에서 만든 각 레코드나 정의를 위해서 한 블럭을 필요로 하기도 한다. 그런데, 당신이 사전에 그것이 얼마나 많을지를 모를 수 있으므로, 각각의 레코드나 정의가 읽혀질 때마다 새로운 블럭을 할당해야만 한다. 당신이 동적 메모리를 할당할 때에는 메모리 한 블럭을 할당할 때마다 프로그램 상에서 분명하게 표현하여야 한다. 당신은 공간을 할당하고 싶을 때마다 함수나 매크로를 호출하여야하며, 인수를 가지고 그 크기를 확정해야 한다. 당신이 그 공간을 해제하고 싶을 때에도, 다른 함수나 매크로를 호출하여 그렇게 처리하면 된다. 당신은 이러한 작업을 원할 때마다 원하는 만큼 할 수 있는 것이다.


3.2 동적 할당과 C

C언어는 C프로그램에 있는 변수들을 통해 메모리 할당을 위한 두 가지 종류를 지원한다.

정적할당은 당신이 정적변수를 정의할 때 발생되는 것이다. 각 정적변수는 정해진 크기의, 메모리 한 블록을 정의한다. 그 공간은 당신의 프로그램이 시작되어 일단 할당되면 절대 해제되지 않는다.

자동 할당은 함수의 인자나 지역변수와 같은 자동변수를 정의할 때 발생한다. 자동변수를 위한 공간은 그 정의를 포함한 곳(compound statement)에 들어가면 할당되고 나오면 해제된다. GNU C에서 자동저장의 길이가 다양하게 표현되어질 수 있지만 다른 C에서는 상수이어야만 한다.

동적 할당은 C변수들에 의해 지원되지 않는다; C에는 "다이내믹"이라는 스토리지 클래스가 없고 동적으로 할당된 공간에 저장된 값은 결코 C의 변수를 통해 할 수 없다. 오직 포인터를 통해 동적으로 할당된 공간을 참조하는 것이 유일한 방법이다. 그래서 좀 불편한데다, 동적 공간을 통해 활동하는 프로세스는 실행시간이 좀 더 필요하기 때문에 프로그래머들은 정적이나 자동변수를 통해 할 수 없을 경우에만 이 동적 할당을 사용한다.

예를 들어 만약 당신이 구조체 foobar를 위해 어떤 공간을 동적으로 할당하기 원한다면 당신은 단순히 struct foobar라고 만 변수를 선언할 수 없다. 그러나 struct foobar * 의형으로 포인터 변수를 선언하면 그 공간의 주소를 지정할 수 있다. 그러면 당신은 *와 -> 오퍼레이터를 사용해서 그 공간의 내용을 참조할 수 있다.

{
struct foobar *ptr
= (struct foobar *) malloc (sizeof (struct foobar));
ptr->name = x;
ptr->next = current_foobar;
current_foobar = ptr;
}


3.3 자유로운 할당

가장 일반적인 동적 할당을 위한 도구는 malloc인데 그것은 어느 크기의 메모리 블록도 언제든지 할당하는걸 허용하고 또한 그들을 언제든지 크게 하거나 작게 하거나 할 수 있고 언제든지 그 불럭들을 개별적으로 메모리 공간을 해제하는 것도 가능하다.

3.3.1 메모리 할당의 기초

메모리 공간을 할당하기 위해 부르는 malloc의 프로토타입은 'stdlib.h' 이다.

void * malloc (size_t size)
이 함수는 새로이 할당된 블록의 포인터를 long의 크기로 반환하고 아니면, 할당할 수 없었을 때는 널 포인터를 반환한다. 할당된 블록은 당신이 스스로 초기화해야한다.( 아니면 calloc을 사용하는데 그것은 3.3.5절의 [Allocating Cleared Space] 를 보면 있다. ) 보통 당신은 메모리 블록에 저장하기 원하는 대상의 종류대로 포인터에 그 값을 캐스트(cast) 할 것이다. 여기에 그렇게 하는 예를 보여준다. 그리고 memset이라는 라이브러리 함수를 이용하여 블록을 0으로 초기화하는 것도 보여준다.
struct foo *ptr;
ptr = (struct foo *) malloc (sizeof (struct foo));
/* 위 라인은 구조체로 선언된 foo의 크기만큼 malloc을 사용해서 메모리를 할당한 다음 구조체 foo의 타입으로 캐스트된 포인터(ptr)에 그 번지 값을 넘기네요.. */
if (ptr == 0) abort ();
/* 만약 할당이 실패하면.. */
memset (ptr, 0, sizeof (struct foo));
/* 위의 memset는 인자로 주어진 포인터의 번지를 받아 맨 마지막 인자로 주어진 크기만큼 0으로 메모리 블록을 초기화해요. */

당신은 캐스트(case)하지 않고 어느 포인터 변수 안에도 malloc의 결과를 저장할 수 있는데 그것은 ANSI C는 자동적으로 필요할 때 void형 포인터를 다른 어떤 형으로도 변환시키기 때문이다. 그러나 캐스트(case)는 지정명령어를 명시하거나 당신이 전통적인 C로 코딩을 원한다면 필요하다. 스트링을 위한 메모리를 할당할 때 malloc의 인수에 스트링의 길이+1을 하는 것을 잊지 마라. 이것은 필요한 공간임에도 불구하고 스트링의 길이에 포함되지 않는 널 문자로 끝나기 때문이다.

/* 보충하면 스트링의 맨 끝에 있는 널 문자를 스트링의 문자열이 끝나는 곳임을 나타내기 위해서 중요한 공간이지만 스트링의 길이를 셀 때는 포함되지 않으므로 스트링을 위해 메모리를 할당할 때는 그 널 문자를 위해 스트링의 길이 +1 만큼의 메모리를 할당받자란 얘기일걸요.*/

예를 들면:
char *ptr;
ptr = (char *) malloc (length + 1);
5.1절 [Ropresentation of Strings] 를 보면 이것에 대한 더 많은 정보가 있다.

 

3.3.2 malloc의 예제들

malloc은 원하는 메모리 블록보다 메모리 공간이 적은 경우에 널을 반환한다. 당신은 모든malloc의 값을 체크해볼 수 있기 때문에 malloc을 부르고 그 값이 널 포인터라면 에러를 사용자에게 보고할 수 있고 아니면 그 값을 오직 반환 하는 서브루틴을 쓰고자 할 때 유용하다. 이 함수는 상투적으로 xmalloc라고 부른다. 여기에 그것이 있다:

void * xmalloc (size_t size)
{
register void *value = malloc (size);
/* 위 라인은 size만큼의 메모리를 할당하려고 malloc을 사용하는 데 그 malloc의 반환 값을 *value로 받았네요. */
if (value == 0) /* value가 0이면, 즉 메모리 할당실패. */
fatal ("virtual memory exhausted");
return value;
/* 할당에 성공하면 블록의 번지가 저장된 값을 반환 */
}

여기에 malloc의 실제 사용 예가 있다. savestring이라는 함수는 널 문자로 끝나는 새로운 할당 공간에 문자들의 열을 복사하는 함수다.

char * savestring (const char *ptr, size_t len)
{
register char *value = (char *) xmalloc (len + 1);
/* 위에서 만든 xmalloc함수를 사용했군요. 그러면 할당에 성공했다면 여기서 value는 len+1의 크기를 가진 블록의 번지를 저장하고 있겠군요. */
memcpy (value, ptr, len);
/* memcpy함수를 사용해서 savestring함수가 인자로 받아온 ptr을 value로 len길이 만큼 복사했고요... */
value[len] = '\0';
/* value의 맨 끝에 널 문자를 하나 저장하면 value는 위에서 설명한대로 스트링을 저장한 블록의 주소가 되겠지요? */
return value;
}

malloc으로 할당된 메모리 블록은 당신에게 어떤 데이터 타입도 저장할 수 있고 그것과 일치시킴을 보증한다. GNU시스템에서 주소는 항상 8의 배수로 증가한다. 그러나 블록이 16개보다 많은 경우에 주소는 항상 16의 배수이다. 오직 드물게 높은 경계가 필요한 경우에는 memalign이나 valloc을 사용한다. ( 3.3.7절의 [Aligned Memory Blocks]를 보라)

무언가를 위해 사용되어진 블록의 끝에 할당된 메모리는 아마도 다른 블록으로 이미 다른 malloc에 의해 할당되어졌을 것이다. 만약 이미 당신이 요구한 것보다 더 많은 메모리 블록을 취급하길 원한다면 자칫하면 내 자신의 블록의 데이터를 파괴하거나 아니면 다른 블록의 내용을 파괴할지도 모른다. 만약 당신이 이미 한 블록을 할당받았고 거기에 더 큰공간이 필요하다면 realloc을 사용하라

 

3.3.3 malloc에 의해 할당된 메모리 해제하기

당신이 malloc으로 얻었던 메모리블럭을 더 이상 사용하지 않으면 free함수를 사용하여 어떤 블록이 그 공간을 다시 할당받을 수 있도록 메모리를 해제해야 한다. free함수의 프로토타입은 'stdlib.h'이다.

void free (void *ptr)

이 함수는 ptr에 의해 지정된 공간의 블록을 해제하는 역할을 한다.

void cfree (void *ptr)

이 함수는 free와 같은 일을 한다. 이것은 SunOS와 호환을 위하여 제공된다. 블럭의 해제 후에 그 블록의 내용은 변한다. 그래서 해제 후에 그 블럭 안에서 어떤 데이터를 발견할지( 마치 연결된 블록에서 다음 블록의 포인터처럼)아무 것도 예상할 수 없다. 당신은 그 블록을 해제하기 전에 그 블록의 밖으로 당신이 필요한 모든 것을 복사하라. 여기에 연결된 모든 블럭과 포인트된 문자열을 해제하는 적당한 방법의 예가 있다.
struct chain
{
struct chain *next;
char *name;
}
/* 단순 연결 리스트( Single Linked List) 구조를 가졌군요. 다시 참조해보세요..*./
void free_chain (struct chain *chain)
{
while (chain != 0)
{
/* chain의 값이 0이 될 때까지 즉 연결된 리스트가 메모리를 다 해제할 때까지 루프를 돌겠군요. */
struct chain *next = chain->next;
free (chain->name);
free (chain);
/* 먼저 안에 저장된 name를 먼저 해제하고(그냥 단순변수가 아니라 포인터로 지정됐으므로) 다음에서야 chain블럭을 해제하는군요. */
chain = next;
/* 그리고 다음을 가리키게 해서 위와 같은 동작의 반복을 하면 다 해제되겠지요? */
}
}

가끔, free함수는 운영체제에게 실제로 메모리를 반환할 수 있고 프로세스를 작게 만들지만 보통은 그 공간의 재사용을 위해 malloc에 의해 다시 불려진 후에 허용된다. 즉, 그 공간(이미 해제된)은 해제된 메모리 리스트를 당신의 프로그램 안에 남겨 다른 malloc에 의해 내부에서 사용된다. 프로그램의 끝에서 블록을 해제할 수 없는데 그것은 프로그램에서 쓰인 모든 공간을 그 프로세스가 끝날 때 시스템에게 되돌려 주어야 하기 때문이다.

 

3.3.5 깨끗한 공간 할당하기

함수 calloc은 메모리를 할당하고 거기에 0을 채운다. 이 함수는 'stdlib.h'에 선언되어 있다.

함수 void * calloc (size_t count, size_t eltsize)

이 함수는 각 eltsize 크기마다 count요소의 벡터를 담기에 충분한 길이의 블럭을 할당한다. 그 내용은 모두 지워지고 calloc을 반환하기 전에 0으로 채운다.
당신은 calloc을 다음과 같이 정의할 수 있다.
void * calloc (size_t count, size_t eltsize)
/*으힉!..size_t라는 데이터 형태도 있었남?..왕초보*/
{
size_t size = count * eltsize;
void *value = malloc (size);
if (value != 0)
memset (value, 0, size);
/* value지점을 size만큼의 0으로 채우는 함수이군*/
return value;
/*이렇게 하면 포인터 주소가 반환되남유? 고수님들?*/
}

요즘은 calloc을 자주 쓰지 않는다, 왜냐하면 더 자주 쓰이는 다른 함수들을 간단히 결합해서 써도 되기 때문이다. calloc은 아주 쓸 모 없지는 않지만 구시대의 유물이 되어가고 있다.

 

3.3.6 malloc을 위한 효율적인 조치

malloc을 가장 잘 사용하려면, malloc의 GNU 버전은 항상 적은 양의 메모리를 그 크기가 2의 배수인 블럭으로 나누는 것임을 알아야 한다. 그것은 분할된 영역을 2의 배수로 유지한다. 이것은 그 크기를 한 의 크기로 만들 수 있게 한다. 그러므로, 만약 당신이 malloc을 효율적으로 만들기 위해서 작은 블럭의 크기를 선택할 수 있는 경우라면 그것을 2의 배수로 만들어라.

일단 한 가 특정한 블럭의 크기로 나누어지게 되면, 그것은 모든 블럭이 해제되지 않는 이상 다른 크기로 재 사용될 수는 없다. 많은 프로그램에서 블럭이 다른 크기로 재 사용되는 일은 좀처럼 일어나지 않는다. 따라서, 많은 다른 목적으로 사용할 블럭들을 같은 크기로 나누어 놓음으로써 프로그램이 메모리를 효율적으로 사용토록 할 수가 있는 것이다.

한 또는 그 이상의 메모리 블럭들을 요구할 때, malloc은 다른 방법을 쓴다; 그 크기를 한 의 배수로 만들고, 그 블럭을 통합하거나 필요한 만큼 나누어서 사용할 수 있다.

두 가지의 방법을 쓰는 이유는 작은 블럭들을 가능한 한 빨리 할당하고 해제하는 것이 중요하다는 데 있다. 그러나 큰 블럭의 경우에는 프로그램이 그것을 사용하는 데 어느 정도의 시간을 소요하기 때문에 속도가 그리 중요치 않다. 또한 큰 블럭은 당연히 그 숫자에 있어서 적은 것이다. 그러므로, 큰 블럭에 있어서는 낭비된 공간을 최소화하는 데 시간을 좀더 투자하는 방법이 의미가 있는 법이다.

 

3.3.7 잘 정돈된 메모리 블럭 할당하기

GNU 체제에서 malloc과 realloc에 의해 반환되는 블럭의 주소는 항상 8의 배수이다. 만약 당신이 8보다 더 큰 2의 배수의 배수를 주소로 갖는 블럭을 원한다면, memalign이나 valloc을 사용하라. 이 함수들은 'stdio.h'에 선언되어 있다.

GNU 라이브러리를 사용해서 당신은 menalign과 valloc이 반환하는 블럭을 해제하기 위하여 free를 쓸 수 있다. free는 BSD에서는 작동하지 않으며,BSD는 그러한 블럭들을 해제하기 위한 어떤 수단도 제공하지 않는다.

함수 void * memalign (size_t size, size_t boundary )

memalign 함수는 그 주소가 boundary의 배수인 size 바이트의 블록을 할당한다. boundary는 2의 배수여야만 한다! memalign 함수는 malloc 함수를 호출하여 다소간 더 큰 블럭을 할당하고 나서, 지정된 boundary상에 있는 블럭내의 주소를 반환한다.

함수 void * valloc (size_t size)

valloc을 사용하는 것은 memalign을 쓰되 두 번째 인수의 값인 크기를 생략하는 것과 같다. 그것은 이렇게 보충된다:
void * valloc (size_t size)
{
return memalign (size, getpagesize ());
}

 

3.3.8 메모리 힙의 일관성 체크하기

당신은 mcheck 함수를 사용하여 동적 저장소의 일관성을 체크하는 데 malloc을 요청할 수 있다. 이 함수는 GNU 확장 함수이며,'malloc.h'에 선언되어 있다.

함수 int mcheck (void (*abortfn) (void))

mcheck을 호출하게 되면 malloc에게 가끔씩 일관성 체크를 수행하도록 한다. 일관성 체크는 malloc이 할당해 놓은 블럭의 끝에다 past라고 기록해 놓는 것과 같은 일을 할 것이다. 인수 abortfn은 불일치가 발견되었을 때 호출되는 함수이다. 당신이 널(null) 포인터를 주게 되면, abort 함수가 사용된다.malloc을 써서 이미 어떤 것을 할당한 뒤에 할당 체크를 시작하기엔 너무 늦었다. 이러한 경우에 mcheck은 아무런 도움이 되지 않는다. 그 함수는 너무 늦게 호출하게 되면 -1을 반환하며, 그렇지 않을 경우에(성공하였을 때) 0을 반환한다. 일찌감치 mcheck을 호출하여 배열하는 가장 손쉬운 방법은 당신이 프로그램을 링크할때 '-lmchek' 옵션을 사용하는 것이다; 그러면 당신은 프로그램 소스를 전혀 변경할 필요가 없다.

3.3.9 저장소 할당 hook들

GNU C 라이브러리는 적절한 hook 함수들을 명시함으로써 당신이 malloc,realloc과 free의 행위를 변경할 수 있도록 해준다. 이러한 hook들은, 예컨대, 동적 저장소 할당을 사용한 프로그램들을 디버그 하는데 도움을 준다. hook 변수들은 'malloc.h'에 선언되어 있다.

변수 ____malloc__hook

이 변수의 값은 malloc이 호출될 때마다 사용하는 함수에 대한 포인터이다. 당신은 이 함수를 malloc과 같은 모양으로 정의할 수 있다.
즉, 다음과 같다;
void *function (size_t size)

변수 ____realloc__hook

이 변수의 값은 realloc이 호출될 때마다 사용하는 함수에 대한 포인터이다. 당신은 이 함수를 realloc과 같은 모양으로 정의할 수 있다.
즉, 다음과 같다;
void *function (void *ptr, size_t size)

변수 ____free__hook

이 변수의 값은 free가 호출될 때마다 사용하는 함수에 대한 포인터이다. 당신은 이 함수를 free와 같은 모양으로 정의할 수 있다.
즉, 다음과 같다;
void function (void *ptr)

당신은 이 함수들 중의 하나의 hook로써 당신이 인스톨한 함수는 그 hook의 이전의 값이 먼저 저장되지 않은 상태에서 재차 그 함수를 호출하지는 않는다는 점을 분명하게 알아두어야 한다! 그렇지 않으면 당신의 프로그램은 무한반복에 빠져 버릴 것이다.

이제 __malloc_hook을 적절하게 사용하는 법을 예를 들어보기로 하자. 그것은 malloc이 호출될 때마다 정보를 기록해내는 함수를 인스톨한다.

static void *(*old_malloc_hook) (size_t);
/* my_malloc_hook 함수의 정의가 시작되는 건가?*/
static void *my_malloc_hook (size_t size)
{
void *result; /*포인터 result 선언해 둔 다음*/
__malloc_hook = old_malloc_hook;
result = malloc (size); /*size크기의 메모리 할당하고*/
__malloc_hook = my_malloc_hook;
printf ("malloc (%u) returns %p\n", (unsigned int) size, result);
/*size와 result의 값을 모니터로 출력한 다음*/
return result; /*result를 반환한다???..void함수가???*/
}
main ()
{
...
old_malloc_hook = __malloc_hook;
/*음..A에다 B를 대입하고 B에다 C를 대입?*/
__malloc_hook = my_malloc_hook;
/*음, 자세히 보면,my_malloc_hook이 작동하면서 새롭게 할당되는 size크기의 메모리를 result에 받아서 __malloc_hook에다 대입시키겠네여..흐흐 ..오리무중!..초보의 설움...우
잉??..__malloc_hook이 인수 size_t size없이도 작동을 하남??..아..살려줘!*/
...
}

mcheck 함수(3.3.8 [메모리 heep 일정성 체크하기] 참조)는 이러한 hook들을 인스톨하여야 작동한다.

 

3.3.10 malloc으로 할당한 저장소 통계

당신은 mstats 함수를 호출함으로써 동적 저장소 할당에 관한 정보를 얻을 수 있다. 이 함수와 그것의 연관된 데이터 형태는 'malloc.h'에 선언되어 있다. 그것들은 GNU의 확장이다.

데이터 형태 struct mstats

이 구조체 형태는 동적 저장소 할당자에 관한 정보를 반환하는데 사용된다. 그것은 다음 멤버들을 담고 있다.

size_t bytes_total

이것은 malloc에 의해 관리되는 메모리의 전체 크기의 바이트 수이다.

size_t chunks_used

이것은 사용된 메모리 덩어리의 숫자이다. (저장소 할당자는 내부적으로 운영체제로부터 메모리 덩어리를 얻어낸 다음, 그것들을 개별적인 malloc의 요구에 만족되도록 나누어준다; 3.3.6 [malloc을 위한 효율적인 조치] 참조)

size_t bytes_used

이것은 사용된 바이트 수이다.

size_t chunks_free

이것은 사용되지 않은 덩어리의 숫자이다.-- 즉, 운영체제에 의해 당신의 프로그램에 할당되었으나, 아직 사용되지 않은 메모리이다.

size_t bytes_free

이것은 사용되지 않은 바이트 숫자이다.

함수 struct mstats mstats (void)

이 함수는 struct mstats 형태의 구조에서 현재의 동적 메모리 사용에 관한 정보를 반환한다.

 

3.3.11 malloc 관련 함수 요약

malloc과 함께 작동하는 함수들을 요약해보자.

void *malloc (size_t size)

size 바이트의 한 블럭을 할당하기. 3.3.1 [기본적인 할당] 참조.

void free (void *addr)

malloc에 의해 이전에 할당되었던 블럭을 해제하기. 3.3.3 [malloc이후의 해제] 참조.

void *realloc (void *addr, size_t size)

malloc에 의해 이전에 할당되었던 블럭을 크게 또는 작게 만들어서, 새로운 지역에 그것을 복사하기.

void *calloc (size_t count, size_t eltsize)

malloc을 사용하여 count * eltsize 바이트의 블럭을 할당하여 그 내용을 제로로 채우기. 3.3.5 [깨끗한 공간 할당하기] 참조.

void *valloc (size_t size)

size 바이트의 블럭을 할당하여, 한 경계지점에서 시작하기. 3.3.7 [잘 정돈된 메모리 블럭] 참조.

void *memalign (size_t size, size_t boundary )

size 바이트의 블럭을 할당하여 boundary의 배수인 주소로 시작하기. 3.3.7 [잘 정돈된 메모리 블럭] 참조.

int mcheck (void (*abortfn) (void))

malloc으로 하여금 수시로 동적으로 할당된 메모리의 일관성을 체크하게 하여 불일치가 일어나면 abortfn을 호출한다. 3.3.8 [메모리 heep 일관성 체크하기] 참조.

void *(*__malloc_hook) (size_t size)

malloc이 호출될 때마다 사용하는 함수에 대한 포인터.

void *(*__realloc_hook) (void *ptr, size_t size)

realloc이 호출될 때마다 사용하는 함수에 대한 포인터.

void (*__free_hook) (void *ptr)

free가 호출될 때마다 사용하는 함수에 대한 포인터.

struct mstats mstats (void)

현재의 동적 메모리 사용에 관한 정보 반환하기. 3.3.10 [malloc으로 할당한 저장소 통계] 참조.


3.4 Obstacks

obstack은 object들의 스택들이 있는 메모리의 공동관리소(pool) 이다. 당신은 obstack들을 분리하여 몇 개라도 만들 수 있고 그리고 정해진 obstack에 object를 할당한다. 각 obstack안에 마지막 objedt에서 첫 번째 하나는 반드시 해제된 상태 이여야 하지만 개별적 obstack들은 다른 것에 독립적이다.

해제에 관한 하나의 제약 말고는 obstack들은 전체적으로 일반적이다; obstack은 어느 크기의 object들을 몇 개라도 저장할 수 있다. 그들은 매크로로 실행되고 그런 할당은 object가 보통 작아서, 매우 빠르다. 그리고 적당한 경계 위에 각 object를 시작하기 위해 필요한 공간이 있는데 그것은 각 대상마다 덧붙여 있다.

3.4.1 Obstacks 만들기

obstacks를 다루기 위한 유틸리티들은 헤더파일 'obstack.h'에 정의되어 있다.

struct obstack Data Type

obstack은 구조체 obstack의 타입으로 데이터 구조를 나타낸다. 이 구조는 작은 정해진 크기를 가진다; 각 레코드들은 obstack의 상황과 할당된 곳에서 공간을 어떻게 발견할 것인가를 나타낸다. 그러나 obstack은 object자체를 포함하지는 않는다. 당신은 직접적으로 그 구조체의 내용을 검색하려고 시도할 수 없다. 오직 이 장에서 설명하는 함수들을 사용하라.

당신은 struct obstack 타입으로 변수들을 선언할 수 있고 obstack들처럼 그들은 사용할 수 있다. 또한 다른 종류의 object처럼 동적으로 obstack들을 할당할 수 있다. obstack의 동적 할당은 몇 개의 다른 스택을 가진 한 변수를 가질 수 있도록 당신의 프로그램에게 허용한다. (당신은 심지어 한 obstack안에 다른 obstack구조를 할당할 수 있다. 하지만 이것은 그리 유용하지는 않다.)

당신이 사용하기 위해 obstack을 요청하기 위해서는 obstack으로 그와 같은 일을 하는 모든 함수들이 필요하다. 당신은 struct obstack * 타입의 포인터로 이것을 한다. 따라서 우리가 종종 "an obstack"라고 말할 때 엄격히 말하면 그것은 포인터와 같다.

obstack안에 object들은 청크(chunks)라고 불리는 큰 블록들 안에 꾸려져 있다. 구조체 obstack은 현재 사용되고 있는 청크들의 연결을 포인터로 구성한다.

obstack 라이브러리는 당신이 앞의 청크에 넣기 원하지 않는 object를 할당할 때마다 새로운 청크를 만든다. obstack 라이브러리가 청크를 관리하므로 당신은 청크에게 많은 주의를 쏟을 필요는 없지만 청크를 얻어서 사용하는 함수를 공급해줄 필요는 있다. 보통 당신은 직접적이거나 간접적이게 malloc을 사용하는 함수를 공급한다. 당신은 청크를 해제하기 위해서도 함수를 공급해야만 한다. 이 문제들은 다음절에 설명되어 있다.

 

3.4.2 Obstack들은 사용하기 위한 준비

당신이 obstack을 사용하려고 계획했다면 각 소스파일 안에 'obstack.h'라는 헤더파일을 포함해야만 한다. 이것은 다음과 같다.

#include <obstack.h>

또한 소스파일에 매크로 obstack_init를 사용한다면 obstack 라이브러리에 의해 불려질 매크로나 두 개의 함수를 선언하거나 정의해야만 한다. 하나는 obstack_chunk_alloc으로 모아져있는( are packed) object들에 메모리의 청크를 할당하기 위해 사용되는 것이다. 다른 하나는obstack_chunk_free로 청크에서 object들을 해제하고 청크를 반환하는데 사용한다.

보통 이들은 xmalloc 이라는 매개자를 거쳐 malloc을 사용하도록 정의되어 있다. ( 3.3장[Unconstrained Allocation] 를 참조) 이것은 밑에 있는 두 개의 매크로 정의로 한다.

#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free

당신이 obstack들을 사용하면서 얻어낸 저장공간은 실제로 malloc을 통해 할당된 것이고, obstak를 사용하는 건 메모리의 큰 블록들은 덜 자주 호출하기 때문에 좀더 빠르다.

실행시 프로그램이 obstack으로 struct obstack타입인 object를 사용하기 전에 obstack_init를 호출해서 obstack을 초기화해야만 한다.

함수 void obstack__init (struct obstack *obstack_ptr)

object의 할당을 위해 obstack_ptr을 초기화

여기에 obstack을 위해 어떻게 공간을 할당하고 그것을 초기화하는 두 가지 예가 있다.

첫 번째, 스택변수인 obstack:
static struct obstack myobstack;
....
obstack_init (&myobstack);
둘째로 obstack 그 자신이 동적으로 할당되는 것:
struct obstack *myobstack_ptr
= (struct obstack *) xmalloc (sizeof (struct obstack));
obstack_init (myobstack_ptr);

3.4.3 Obstack에서 할당

obstack에 object를 할당하기 위한 가장 직접적인 방법을 obstack_alloc을 사용하는 건데 이것은 거의 malloc과 같다.

함수 void * obstack__alloc (struct obstack *obstack_ptr, size_t size)
몇 바이트의 크기인지 초기화되지 않은 할당은 그 자신의 주소 값을 반환한다. 여기의 obstack_ptr은 안에 블록을 할당하기 위한 obstack을 지정한다.; 그것은 obstack으로 표현된 struct obstack 타입의 object의 주소이다. 각각의 obstack 함수나 매크로는 당신에게 첫 번째 인자로obstack_ptr을 지정할 것을 요청한다.
여기의 예는 정해진 obstack, 즉 변수 string_obstack안에 문자열을 할당하기 위한 함수를
보여준다.
/*또 역시 제가 주석을 좀 붙여 보지요.. 헤헤..*/
struct obstack string_obstack;
/* struct obstack 형으로 string_obstack변수를 선언했네요. */
char * copystring (char *string)
{
char *s = (char *) obstack_alloc (&string_obstack, strlen (string) + 1);
/* 아까 위에서 obstack_alloc이 거의 malloc 과 같다고 했지요..? 그러니까 문자열을 위해 공간을 할당받기 위해서는 길이 +1 해서 포인터를 반환 하면 s로 받지요...*/
memcpy (s, string, strlen (string));
/* 위에서 받은 포인터 s에 string 을 길이만큼 복사 */
return s; /* 그리고 반환...*/
}
정해진 내용을 갖은 블록을 할당하기 위해서는 obstack_copy 함수를 사용하라. 이것은 다음과 같이 정의한다.

void * obstack__copy (struct obstack *obstack_ptr, void *address, size_t size)

이것은 블록을 할당하고 그 주소에 문자열을 정해진 크기대로 복사해서 초기화하는 것이다.

void * obstack__copy0 (struct obstack *obstack_ptr, void *address, size_t size)

obstack_copy와 같지만 널 문자를 포함하는 하나의 여분의 바이트를 덧붙인다. 이 여분의 바이트는 인수크기에는 들어가지 않는다. 이 obstack_copy() 함수는 널 문자로 끝나는 문자열과 같은 문자의 열들을 obstack에 넣기에는 편리한 함수다.
사용 예를 보면:
char * obstack_savestring (char *addr, size_t size)
{
return obstack_copy0 (&myobstack, addr, size);
}
malloc을 사용한 앞의 stvestring 의 예와 비교해 보라 (3.3.1절의 [Basic Allocation]을 보라)

 

3.4.4 Obstack에서 Objects 해제하기

obstack안에 할당된 object를 해제하기 위해서는 obstack_free함수를 사용한다. object들의 스택인 obstack은 한 object가 해제되면 그 object이후 최근에 같은 obstack에 할당된 다른 object들도 자동적으로 해제시킨다.

함수 obstack__free (struct obstack *obstack_ptr, void *object)

만약 object가 널 포인터이면 obstack안에 할당된 모든 것이 해제된다. 그렇지 않으면 object는 obstack안에 할당된 object의 주소 이여야만 한다. 그러면 object는 해제되고 그 해제된 object이후 obstack안에 할당된 모든 것도 따라서 해제된다.
object에 널 포인터를 주면 그 결과는 obstack의 비 초기화라는 것을 알아라. obstack안에 모든 내용은 해제하지만 다음 할당을 위해 obstack을 유용하게 남기고 싶다면 obstack에 할당된 object의 첫 번째 주소를 주어 obstack_free를 호출하면 된다.
청크안에 모아진 obstack안의 object들은 재 호출하라. 청크안에 object들은 모두 해제할 때 그 obstack 라이브러리는 자동적으로 청크를 해제한다. ( 3.4.2절 [Preparing for Obstacks] 참조) 그러면 다른 obstack들이나 비 obstack 할당도 그 청크의 공간을 재사용 할 수 있다.

3.4.5 Obstack 함수와 매크로

obstack들의 사용을 위한 인터페이스는 컴파일러에 의존하는 함수나 매크로로 정의되어 진다. 모든 C 컴파일러는 ANSI C와 전통적인 C를 지원하는 obstack 도구를 가지고 있다. 하지만 당신이 GNU C 보다는 다른 컴파일러를 사용하려 한다면 많은 주의를 해야한다.

만약 당신이 오래된 비 ANSI C 컴파일러를 사용하면 모든 obstack 함수들은 매크로로 정의되어 있다. 당신은 이 매크로를 함수들처럼 호출 할 수 있지만 당신은 그것을 다른 방법으로는 사용할 수 없다.( 예를 들어, 그들의 주소를 줄 수 없다. )

매크로의 호출은 특별한 주의가 요구된다.; 즉, 첫 번째 피연산자 ( obstack 포인터)가 부작용을 포함하고 있다고 할 수 있을지도 모르는데, 그것은 그것이 여러 번 연산되기 때문이다. 예를 들어 만약 당신이 아래처럼 쓰면;

obstack_alloc (get_obstack (), 4);

당신은 여러 번 get_obstack이 호출되는걸 발견할 수 있다. 만약 당신이 obstack 포인터 인자처럼 *obstack_list_prt++을 사용하면 여러 번 증가가 발생하여 이상한 결과를 얻을 것이다.

ANSI C는 함수와 매크로 정의 둘을 다 지원한다. 함수 정의는 그것을 호출하지 않고 함수의 주소로 사용되는 것이다. 보통의 호출은 암묵적으로 매크로 정의가 사용되지만 당신은 괄호 안에 함수의 이름을 씀으로써 함수정의를 요청할 수 있다. 아래처럼;

char *x;
void *(*funcp) (); /* 매크로 사용 */
x = (char *) obstack_alloc (obptr, size); /* 함수 호출 */
x = (char *) (obstack_alloc) (obptr, size); /* 함수의 주소를 취하라 */
funcp = obstack_alloc;

이것은 표준 라이브러리 함수를 위한 ANSI C안에 있는 것과 같다. 1.3.2절[Macro Definitions] 를 보라.

 
주의 : 심지어 ANSI C에서 조차도 첫 번째 피연산자의 부작용을 피하는데 주의를 기울여야 한다. 만약 당신이 GNU C컴파일러를 사용하고 있다면 이 주의는 필요하지 않는데, 그것은 GNU C안에 다양한 언어의 확장은 오직 한번 각 인수를 실행하도록 그 매크로를 정의하는 것을 허용한다.

 

3.4.6 성장하는 대상물

obstack 덩어리 내의 저장소는 연속적으로 사용되기 때문에 어떤 대상물을 차례로 쌓아서 대상물의 끝에 한번에 하나 또는 그 이상의 바이트를 추가할 수 있다. 이 기법을 사용함에 있어 당신은 그 대상물의 끝에 다다를 때까지 그 대상물에 얼마만큼의 데이터를 넣을 수 있는지를 알 필요가 없다. 이것은 성장하는 대상물 기법이라 불린다. 성장하는 대상물에 데이터를 추가하는 특수한 함수들은 이 절에 기술되어 있다. 당신은 어떤 대상물을 성장시키기 시작할 때 아무런 특별한 조치도 할 필요가 없다. 그 대상물에 데이터를 추가시키는 함수들 중의 하나를 사용하게 되면 그것은 자동적으로 시작된다. 그러나 그 대상물이 끝날 때에는 분명하게 말해 줄 필요가 있다. 이것은 함수 obstack_finish에 의해 수행된다. 이렇게 해서 만들어진 대상물의 실제 주소는 그것이 끝나기 전에는 알 수가 없다. 그때까지는 항상 대상물이 새로운 덩어리 속으로 복사되어야 할만큼의 데이터만이 추가될 수 있을 뿐이다. obstack이 성장하는 대상물을 위해 사용되고 있을 동안에는 당신은 다른 대상물을 할당하기 위해서 그것을 사용할 수는 없다. 만약 그렇게 한다면, 이미 성장하는 대상물에 추가된 공간이 다른 대상물의 일부분으로 되어버릴 것이다.

함수 void obstack__blank (struct obstack *obstack_ptr, size_t size)

성장하는 대상물에 추가하기 위한 가장 기초적인 함수는 obstack_blank이며, 이것은 대상물을 초기화하지 않고 공간을 추가한다.

함수 void obstack__grow (struct obstack *obstack_ptr, void *data, size_t size)

한 블럭의 초기화된 공간을 사용하려면,obstack_grow를 사용하라.obstack_grow는 성장하는 대상물에 있어서는 obstack_copy와 유사하다.obstack_grow는 성장하는 대상물에 size 바이트의 데이터를 추가하고 데이터로부터 그 내용을 복사한다.

함수 void obstack__grow0 (struct obstack *obstack_ptr, void *data, size_t size)

이것은 성장하는 대상물에 있어서 obstack_copy와 유사하다. 그것은 널(null) 문자가 따라오는 데이터에서 복사한 size 바이트를 추가한다.

함수 void obstack__1grow (struct obstack *obstack_ptr, char c)

한번에 하나의 문자를 추가하려면 함수 obstack__1grow를 사용하라. 그것은 성장하는 대상물에 c를 담아둘 1 바이트를 추가한다.

함수 void * obstack__finish (struct obstack *obstack_ptr)

당신이 대상물 성장시키기를 끝마쳤을 때 함수 obstack_finish를 사용해서 그것을 닫고 그것의 마지막 주소를 반환하라. 당신이 그 대상물을 완료하면,obstack은 또다른 대상물을 할당하거나 성장시키는데 사용될 수 있게 된다. 당신이 하나의 대상물을 성장시켜서 만들게 되면, 그것이 얼마큼 길어졌는지를 나중에 알 필요가 생길 것이다. 당신은 그 대상물이 성장하는 동안에는 그 과정을 추적할 필요가 없다. 왜냐하면 당신은 함수 obstack_object_size를 사용하여 그 대상물의 완료 직전에 obstack으로부터의 길이를 알 수 있기 때문이다. 그것은 다음과 같이 선언된다.

함수 size_t obstack__object__size (struct obstack *obstack_ptr)

이 함수는 성장하는 대상물의 현재 크기를 바이트 수로 반환한다. 이 함수는 대상물이 완료되기 전에 호출하여야 함을 기억하라. 완료되고 난 다음에는 이 함수는 제로를 반환할 것이다. 만약 당신이 대상물 성장시키기를 시작한 다음에 이를 취소하고 싶다면, 당신은 이렇게 끝내고 해제하라;
obstack_free (obstack_ptr,obstack_finish (obstack_ptr));
이것은 아무런 대상물도 성장하고 있지 않을 때에는 아무런 효과도 없는 것이다. 현재의 대상물을 작아지게 만들고 싶으면 음수의 size 인수를 갖는 obstack_blank를 사용할 수 있다. 그러나 그 대상물 자체를 음수 크기로 만들려고 하지는 말아야 한다. 그렇게되면 무슨 일이 일어날는지 알 수가 없다.

 

3.4.7 특히 급속하게 성장하는 대상물

성장하는 대상물을 만드는 통상적인 함수들은 현재의 메모리 덩어리에 새로운 성장을 위한 공간이 있는지를 체크하기 위하여 부가물을 초래한다. 만약 당신이 대상물을 조금씩 단계별로 만들고자 한다면 이 부가물은 중요한 것이 될 수 있다.

당신은 체크하지 않고 대상물을 성장시키는 특수한 "급속한 성장" 함수를 사용함으로써 부가물을 줄일 수 있다. 견실한 프로그램을 만들기 위해서는 당신 자신이 체크를 수행해야만 한다. 만약 당신이 대상물에 데이터를 추가하려 할 때마다 가장 간단한 방법으로 이러한 체크를 수행하려 한다면, 당신은 아무 것도 저장하지 않아도 통상적인 성장 함수들이 이를 행할 것이다. 그러나 당신이 체크의 횟수를 줄이되 더 효과적으로 체크할 수 있도록 배열한다면, 당신은 프로그램을 빠르게 만드는 것이 된다. 함수 obstack_room은 현재의 메모리 덩어리에서 사용 가능한 공간의 양을 반환한다. 그것은 다음과 같이 선언된다:

size_t obstack__room (struct obstack *obstack_ptr)

이것은 가장 빠른 성장 함수를 사용하는 obstack에서 현재의 성장하는 대상물(또는 시작하려하는 대상물)에 안전하게 추가할 수 있는 바이트 수를 반환한다. 공간이 있음을 알 수 있는 한, 당신은 어떤 성장하는 대상물에 데이터를 추가하는 급속한 성장 함수들을 사용 할 수 있다.

void obstack__1grow__fast (struct obstack *obstack_ptr, char c)

함수 obstack_1grow_fast는 구조체 obstack의 포인터 obstack_ptr에서 성장하는 대상물에 문자 c를 담아둘 1 바이트를 추가한다.

함수 void obstack__blank__fast (struct obstack *obstack_ptr, size_t size)

함수 obstack_blank_fast는 구조체 obstack인 obstack_ptr에서 성장하는 대상물에 size 바이트를 초기화하지 않고 추가한다. 당신이 obstack_room을 사용하여 공간을 체크해서 당신이 추가하기를 원하는 만큼의 충분한 공간이 없을 때는, 급속한 성장함수들은 안전하게 사용할 수가 없다. 이러한 경우에는 그저 적합한 통상적인 성장함수만을 사용하라. 이 함수는 곧바로 대상물을 새로운 메모리 덩어리에 복사한다, 그리고 나면 다시 사용 가능한 공간이 많이 있게 될 것이다. 그러고 난 다음에, 당신이 통상적인 성장 함수를 사용할 때마다, 함수 사용 후에 obstack_room을 써서 충분한 공간이 남아있는지를 체크하라. 일단 대상물이 새로운 덩어리로 복사되고 나면 다시 충분한 공간이 생길 것이고, 그래서 프로그램은 다시 급속한 성장함수들을 사용하기 시작할 것이다.
예를 들어보자.
/*(1)함수명:add_string(2)인수1:구조체 obstack을 포인터로 선언(3)인수2:문자포인터ptr을 전달*/
void add_string (struct obstack *obstack, char *ptr, size_t len)
{
while (len > 0) {
/*함수 전체 내용이 결국은 while루프이군 따라서 인수 len은 양수여야만 의미가 있군*/
/* 함수 obstack_room은 그 인수로 받은 obstack 내의 사용 가능한 공간을 바이트 수로 반환하므로 그 바이트 수가 len보다 크면..흐흐..공간이 있지여?*/
if (obstack_room (obstack) > len) { /* 공간이 충분하면 급속한 성장함수 사용*/
while (len-- > 0)
/* 1 만큼씩 헤아리면서 인수로 전달받은 문자 포인터의 내용을 하나씩 obstack에 추가시킨다..그러나 이 루프가 작동을 마칠 때면 결국은 한꺼번에 포인터의 모든 내용이 추가되어 버리는 셈이군 */
obstack_1grow_fast (obstack, *ptr++);
} else {
/* 공간이 불충분하면 서서히 한 문자를 추가하다가 그것을 새로운 덩어리로 복사해서 공간을 만듦 */
obstack_1grow (obstack, *ptr++);
/* 즉, 일단 인수의 포인터의 1문자를 obstack에 추가하고 */
len--;
/* len을 1만큼 줄여서 obstack_room이 공간을 다시 평가하도록 하는군요.-->while루프--> if문장으로 복귀 */
}
} /*while루프의 끝지점*/
} /*함수 전체의 끝지점*/

 

3.4.8 obstack의 위상

하나의 obstack에서 현재의 할당 위상에 관한 정보를 제공하는 함수들이 있다. 당신은 성장하고 있는 대상물에 대해서 알아보기 위해서 그것들을 사용할 수 있다.

함수 void * obstack__base (struct obstack *obstack_ptr)

이 함수는 구조체 obstack_ptr에서 현재 성장하는 대상물의 임시 시작주소를 반환한다. 만약 당신이 그 대상물을 즉시로 종결한다면 그것은 그 주소를 가질 것이다. 반대로 당신이 그것을 더 크게 만든다면 그것은 현재의 덩어리보다 더 성장할 것이다. 그리하여 그 주소가 변할 것이다. 아무런 대상물도 성장하지 않고 있다면, 이 값은 당신이 할당한 다음 번의 대상물이 어디서부터 시작할 것인가를 알려준다.(다시 한번 생각해보면 그것은 곧 현재의 덩어리이다.)

함수 void * obstack__next__free (struct obstack *obstack_ptr)

이 함수는 구조체 obstack_ptr의 현재 덩어리에서 해제된 첫 바이트의 주소를 반환한다. 이것은 현재 성장하고 있는 대상물의 끝 부분이다. 만약 아무런 대상물도 성장하고 있지 않다면, obstack_next_free는 obstack_base와 같은 값을 반환한다.

함수 size_t obstack__object__size (struct obstack *obstack_ptr)

이 함수는 현재 성장하고 있는 대상물의 바이트에서 크기를 반환한다. 이것은 obstack_next_free (obstack_ptr) 에서 obstack_base (obstack_ptr) 을 뺀 것과 같다.
<번역자 주석>
위의 내용들을 종합해 보면, 결국 이런 내용인가?
--> 스택(stack)에 메모리를 확보하고 추가하고 해제한다?

따라서, 위의 스택에 담긴 대상물의 크기는, "시작점(해제지점) - 종료지점"이 되는 건가? 음...,스택과 그 주소를 잘 이해하고 있는 고수들만이 제대로 이해할 수 있겠군...

 

3.4.9 obstack에서의 데이터 할당

각 obstack은 할당 경계지점을 갖는다; obstack에 자동적으로 할당된 각 대상물은 설정된 경계지점의 배수로 된 주소로 시작한다. 디폴트값으로 이 경계지점은 4 바이트 수이다.

obstack의 할당 경계지점에 접근하기 위해서는 매크로 obstack_alignment_mask를 사용하라. 이 함수의 원형은 이렇게 되어있다.

매크로 int obstack__alignment__mask (struct obstack *obstack_ptr)

    그 값은 한 비트의 mask이다; 1로 된 비트는 대상물의 주소에서 대응하는 비트가 0 이어야 함을 나타낸다. mask 값은 2의 배수보다 작은 값이어야 한다; 그 결과 모든 대상물의 주소는 2의 배수의 배수로 된다. mask의 디폴트값이 3으로 되면 주소들은 4의 배수로 된다. mask의 값이 0으로 되면, 대상물이 1의 배수로 시작할 수 있다.(즉, 할당이 되지 않는다.)

    매크로 obstack_alignment_mask의 확장은 lvalue이므로, 당신은 mask를 변경 지정할 수 있다. 예를 들면 다음과 같다;

    obstack_alignment_mask (obstack_ptr) = 0;

    위와 같은 문장은 설정된 obstack에서 진행중인 할당을 없애버리는 효과를 갖는다.

    할당 mask에서 일어난 변화는 어떤 대상물이 obstack에서 할당되거나 종료되기까지는 아무런 효과도 없다. 만약 당신이 어떤 대상물을 성장시키고 있지 않을 경우라면, 당신은 obstack_finish를 호출하여 새로운 할당 mask가 즉각 효과를 갖도록 할 수 있다. 이것은 길이가 제로인 대상물을 종료하고 다음 대상물을 위한 적절한 할당을 하는 것이다.

3.4.10 obstack 덩어리

obstack은 자체의 기능으로 큰 덩어리에 공간을 할당하고 당신의 요청에 부응하여 덩어리에서 공간을 분배한다. 덩어리들은 당신이 별도의 크기를 지정하지 않는 이상 4096 바이트의 크기이다. 덩어리 크기는 대상물들을 저장하는 데에 실제로 사용되지 않는 8 바이트의 부가물을 포함한다. 설정된 크기에 관계없이, 큰 대상물을 위해서 필요하다면 더 큰 덩어리들이 할당되어질 것이다.

obstack 라이브러리는 함수 obstack_chunk_alloc을 호출하여 덩어리들을 할당하는데, 당신이 그것을 정의하여야 한다. 당신이 어떤 덩어리에 있는 대상물을 해제한 결과로서 어떤 덩어리가 더이상 필요치 않게 되면, obstack 라이브러리는 함수 obstack_chunk_free를 호출하여 그 덩어리를 해제한다.

물론 당신은 그것 또한 정의하여야 한다. 이들 둘은 obstack_init(3.4.1 [obstack 만들기] 참조) 을 사용하는 각 소스파일에서 (매크로로)정의되거나 (함수로)선언되어야만 한다. 그것들은 거의 대부분 아래와 같이 매크로로 정의된다:

#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free

이들은 (인수가 아니라) 단순히 매크로임을 주의하라. 인수를 갖는 매크로 정의는 유효치 않다! 다만 obstack_chunk_alloc 나 obstack_chunk_free는 그 자체가 함수명칭이 아닐 경우에 함수 명칭으로 확장될 필요가 있다. 실제로 obstack_chunk_alloc을 사용하는 함수는 어떤 형태로든 "실패"를 반환할 수 없다. 왜냐하면 obstack 라이브러리는 실패를 취급할 준비가 되어있지 않기 때문이다. 그러므로 malloc 그 자체는 적합하지가 않다. 만약 그 함수가 공간을 확보하지 못하게 되면, 그것은 처리를 종료하거나(22.3 [프로그램 종료] 참조.) longjmp를 사용하는 비지역적 종료를 행하게 된다.(20장 [비지역적 종료] 참조)

만약 당신이 malloc을 써서 덩어리들을 할당한다면, 덩어리 크기는 2의 배수여야만 한다. 덩어리의 디폴트 크기는 4096으로 선택되어 있다. 왜냐하면 디폴트 크기만으로도 현재로서는 작은 대상물에서 요구되어질 대부분의 전형적인 요청들을 만족시킬 수가 있으며, 아직은 그 대상물의 크기가 사용되지 않은 가장 최근의 덩어리 부분에 너무 많은 메모리가 낭비되지 않게 해도 될 만큼 작기 때문이다.

매크로 size_t obstack__chunk__size (struct obstack *obstack_ptr)

이것은 주어진 obstack의 덩어리 크기를 반환한다. 이 매크로는 lvalue로 확장되므로, 당신은 그것에 새로운 값을 할당함으로써 새로운 덩어리 크기를 규정할 수 있다. 그렇게 하게 되면 이미 할당되어있는 덩어리에는 영향이 없고, 다만 장래에 특정한 obstack을 위하여 할당될 덩어리들의 크기에만 영향을 주게 된다. 그것은 덩어리 크기를 작게 만드는 데에는 소용이 없다. 만약 당신이 그 크기가 덩어리 크기와 비슷한 많은 대상물을 할당하고 싶다면 덩어리 크기를 크게 만드는 것이 매우 효과적일 것이다.

 

3.4.11 obstack 함수 요약

obstack과 관련된 함수들을 요약해보자. 각 함수들은 첫 인수로서 obstack(구조체 포인터로서)의 주소를 갖는다.

void obstack_init (struct obstack *obstack_ptr)

obstack의 사용을 초기화한다. 3.4.1 [obstack 만들기] 참조.

void *obstack_alloc (struct obstack *obstack_ptr, size_t size)

초기화되지 않은 바이트 크기의 대상물을 할당한다. 3.4.3 [obstack 할당] 참조.

void *obstack_copy (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트 크기의 대상물을 할당하고 주소에서 복사한 내용을 채운다. 3.4.3 [obstack 할당] 참조.

void *obstack_copy0 (struct obstack *obstack_ptr, void *address, size_t size)

size+1 바이트 크기의 대상물을 할당하고 주소에서 복사한 그만한 크기의 내용을 채우고 끝에 널(null) 문자를 붙인다. 3.4.3 [obstack에서의 할당] 참조.

void obstack_free (struct obstack *obstack_ptr, void *object)

대상물 해제(동시에 대상물보다 더 최근에 설정된 obstack에 할당된 모든 것을 해제) 3.4.4 [obstack 대상물 해제하기] 참조.

void obstack_blank (struct obstack *obstack_ptr, size_t size)

성장하는 대상물에 초기화되지 않은 size 바이트를 추가. 3.4.6 [성장하는 대상물] 참조.

void obstack_grow (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트를 주소로부터 복사하여 성장하는 대상물에 추가. 3.4.6 [성장하는 대상물] 참조.

void obstack_grow0 (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트를 주소로부터 복사하여 성장하는 대상물에 추가하고 널(null) 문자를 담고있는 다른 바이트를 덧붙인다. 3.4.6 [성장하는 대상물] 참조.

void obstack_1grow (struct obstack *obstack_ptr, char data_char)

성장하는 대상물에 data_char를 담고있는 한 바이트를 추가. 3.4.6 [ 성장하는 대상물] 참조.

void *obstack_finish (struct obstack *obstack_ptr)

성장하고 있는 대상물을 종결하고 그것의 최후주소를 반환 3.4.6[성장하는 대상물] 참조.

size_t obstack_object_size (struct obstack *obstack_ptr)

현재 성장하고있는 대상물의 현재의 크기를 구한다. 3.4.6 [성장하는 대상물] 참조.

void obstack_blank_fast (struct obstack *obstack_ptr, size_t size)

충분한 공간이 있는지의 여부를 체크하지 않고서, 성장하는 대상물에 초기화되지 않은 size 바이트를 추가. 3.4.7 [급속한 성장함수] 참조.

void obstack_1grow_fast (struct obstack *obstack_ptr, char data_char)

충분한 공간이 있는지의 여부를 체크하지 않고서, 성장하는 대상물에 data_char를 담을 한 바이트를 추가. 3.4.7 [급속한 성장함수] 참조.

size_t obstack_room (struct obstack *obstack_ptr)

현재의 대상물을 성장시키는 데 사용할 수 있는 공간의 양을 확보. 3.4.7 [급속한 성장함수] 참조.

int obstack_alignment_mask (struct obstack *obstack_ptr)

대상물의 시작점을 정돈하기 위해 사용되는 mask. 이것은 lvalue이다. 3.4.9 [obstack에서의 데이터 할당] 참조.

size_t obstack_chunk_size (struct obstack *obstack_ptr)

덩어리 할당을 위한 크기. 이것은 lvalue이다. 3.4.10 [obstack 덩어리] 참조.

void *obstack_base (struct obstack *obstack_ptr)

현재 성장하는 대상물의 임시 시작주소. 3.4.8 [obstack의 위상] 참조.

void *obstack_next_free (struct obstack *obstack_ptr)

현재의 성장하는 대상물이 끝나는 바로 다음의 주소. 3.4.8 [obstack의 위상] 참조.


3.5 다양한 크기로의 자동저장

불충분한 동적 할당을 지원하는 함수들 중의 하나인 alloca는 동적으로 할당을 하지만 자동적으로 해제되지는 않는다. alloca를 가지고 블록을 할당하는 것은 명백한 동작이다; 당신은 원하는 만큼의 많은 블록을 할당할 수 있고 실행 시에 그 크기를 계산할 수도 있다. alloca로 할당된 공간의 변수가 단지 그 함수 안에서 자동변수라고만 선언이 되었다면 그렇게 할당된 블록들은 함수를 빠져나갈 때 모두 해제된다. 하지만 다른 경우는 명백하게 그 공간을 해제하는 방법이 없다. alloca의 프로토타입은 'stdlib.h'이다. 이 함수는 BSD확장이다.

함수 void * alloca (size_t size);

alloca는 불려진 함수의 스택프레임안에 몇 바이트의 크기를 가진 블록을 할당하고 그 블록의 주소 값을 반환한다. 예측할 수 없는 결과를 얻을지도 모르는 함수들의 인수내부에 alloca를 사용하지 말라. 왜냐하면 alloca를 위한 스택의 공간은 함수인수들을 위한 그 공간의 중간에 만들어지기 때문이다. 피해야할 예는 foo (x, alloca(4), y)이다.

 

3.5.1 alloca 예

alloca의 사용 예로서 여기에 한 함수가 있다. 이 함수는 인수로 받은 두 개의 문자열을 붙여서 만든 것을 파일이름으로 해서 그 파일을 열고 그것을 파일기술자에 전달하거나 실패한 경우는 -1을 반환 하는 함수이다.

/* 역시 제가 주석을 좀..주석이 틀렸으면 저에게 왕창 욕해주셔요... */
int open2 (char *str1, char *str2, int flags, int mode)
{
/* alloca를 가지고 두 개의 문자열 길이에 각각 1을 더한 길이만큼의 공간을 char형으로 할당받고 그 포인터를 name에게 주었어요.*/
char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
/* strcpy는 문자열을 복사하고 strcat는 문자열을 덧붙이는 함수 */
strcpy (name, str1);
strcat (name, str2);
/* 복사하고 덧붙여서 만든 name을 파일이름으로 해서 파일하나를 열고 그것을 반환 */
return open (name, flags, mode);
}
이건 malloc을 사용해서 어떻게 같은 결과를 얻고 해제하는지에 대한 예.
int open2 (char *str1, char *str2, int flags, int mode)
{
/* 말했듯이 malloc을 이용해서 필요한 공간을 할당하네요..*/
char *name = (char *) malloc (strlen (str1) + strlen (str2) + 1);
int desc;
if (name == 0) /* 만약 공간이 할당되지 않았으면 에러처리 */
fatal ("virtual memory exceeded");
strcpy (name, str1);
strcat (name, str2);
desc = open (name, flags, mode); /* 일시적으로 파일 핸들을 decs 에 저장 */
free (name);
return desc;
}

위에서 본 것처럼 alloca를 이용하면 더 간단하게 구현할 수 있겠지만 alloca를 사용하는 것은 많은 이득이 있는 대신에 몇 가지 손해도 얻을 수 있다는 걸 명심하라.

 

3.5.2 alloca를 이용해서 얻는 이득

여기에 malloc보다는 alloca를 선택하게 되는 이유가 있다.

alloca를 사용하면 매우 적은 공간을 소비하고 매우 빠르다. ( 그것은 alloca가 GNU C 컴파일러 상에서 open-coded 이어서..) alloca는 블록을 여러 크기로 나누지 않고 이미 있는 다른 크기의 블록을 재 사용할 수 있기 때문에 메모리 단편화의 원인이 되지 않는다. 호출된 alloca를 통해 만들어진 할당된 공간은 longjmp( 20장 [Non-Local Exits]를 보라. )로 자동적으로 해제할 수 있는 Nonlocal이 있다.

참고로 Nonlocal의 의미를 잘 몰라서 책들을 뒤져보니까 이런 게 나와 있네요.

nonlocal: 구조화된 프로그래밍 언어를 사용하여 작성된 프로그램의 어떤 블록에서 자기 블록에 정의되어 있지 않은 변수를 참조하는 것이라고요... 하지만 여기의 Nonlocal을 의미하는지는 잘 모르겠어요. 제 생각에는 얼추 비슷할 것 같은데..

이것을 구현하기 위해서, 호출에 성공하면 open함수처럼 한 디스크립터를 반환하고 실패하면 아무 것도 반환하지 않는 opne_or_report_error 이라는 함수가 있다고 가정하라. 만약 호출에 실패해서 파일이 열려지지 않으면, 에러 메시지를 프린트하고 longjmp를 사용해서 당신의 프로그램의 한 레벨을 빠져나온다. 이것을 사용해서 open2를 바꾸어보자.

subroutine:
int open2 (char *str1, char *str2, int flags, int mode)
{
char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
strcpy (name, str1);
strcat (name, str2);
/* 아까 open2 함수에서 변화된 것은 open함수가 open_or_report_error로 대치된 거네요. */
return open_or_report_error (name, flags, mode);
}

좀더 자세히 제가 이해한대로 보충설명을 드리자면... 실제로 open_or_report_error 이라는 함수가 존재하지 않는데 그냥 있다고 가정하고 쓰면 프로그램 내에서 그것을 호출할 수 없으므로 에러가 나겠지요. 그런데 그 alloca란 놈이 일단 에러가 나면 alloca를 통해 할당된 모든 공간을 해제하고 빠져 나올 수 있기 때문에, 그리고 alloca는 명백하게 공간은 해제해주는 기능이 없으니까.. 이런 편법을 사용하게 되나봐요.

alloca를 통한 작업에서 이 방법을 사용하는 이유는 alloca를 통해 할당된 공간은 에러가 발생하면 어떤 특별한 노력 없이 할당된 공간을 해제할 수 있기 때문이다. 앞의 open2와 비교해서( malloc과 free를 사용하는 ) 이 방법을 사용하면 저장공간의 유출을 발견할 수 있을 것이다. 그러나 당신이 좀더 많은 변화를 원한다면 그렇게 하는 쉬운 방법은 아무 것도 없다.

 

3.5.3 alloca의 단점

이곳은 malloc과 비교하여 alloca의 단점을 보여준다. 만약 당신이 alloca를 가지고 시스템이 제공할 수 있는 것보다 더 많은 저장공간을 할당받길 원하면 당신은 명백한 에러 메시지를 얻을 수 없다. 대신에 당신은 무한 재귀호출에서 얻을 수 있는 것과 같은 심각한 신호를 얻을 것이다.; 아마도 segmentation 위배 ( 21.2.1절[ 프로그램 에러 신호] 참조) 어떤 비GNU 시스템들은 이 alloca를 지원하지 않아서 이식성의 측면에서는 좀 떨어진다. 그렇지만 alloca는 이런 결점에도 불구하고 유용하게 사용된다.

 

3.5.4 GNU C 변할 수 있는 크기의 배열

GNU C에서 당신은 alloca의 사용을 변화 가능한 크기를 갖는 배열을(an array of variable size) 사용해서 대체할 수 있다. 여기에 그것을 보여주는 예가 있다.

int open2 (char *str1, char *str2, int flags, int mode)
{
/* 위에서 alloca를 사용해서 할당했던 공간을 대신 크기가 'str1의 문자길이 + str2의 문자길이 + 1'인 배열을 만들었네요.*/
char name[strlen (str1) + strlen (str2) + 1];
strcpy (name, str1);
strcat (name, str2);
return open (name, flags, mode);
}

그러나 alloca는 여러 가지 이유로 변화 가능한 크기를 갖는 배열로 항상 대체할 수 있는 것은 아니다. 변화 가능한 배열에게 할당된 메모리 공간은 배열이 선언된 영역의 끝에서 해제되지만 alloca로 할당된 공간은 함수가 끝날 때까지 남아 있다.

반복을 사용했을 때 각 반복마다 더해서 블록을 할당하는 loop에서 alloca를 사용하는 것은 가능하다. 하지만 변화 가능한 크기를 갖는 배열로는 불가능하다.

주의 : 만약 당신dl alloca와 변화 가능한 크기를 갖는 배열을 한 함수 안에서 혼용하면, 변화 가능한 크기를 갖는 배열이 선언된 영역을 벗어날 때 그 영역이 실행되는 동안에 alloca로 할당했던 모든 블록들도 해제된다는 것을 명심하라.


3.6 재조정 할당자

어떠한 체제의 동적 메모리 할당이든 간에 부가물을 갖는다: 그것이 사용하는 공간의 양은 프로그램이 요구하는 공간의 양보다 크다. 메모리 재조정 할당자는 독자적 판단으로 필요한 만큼의 메모리 블럭을 이동시킴으로써 부가물을 최소화한다.

 

3.6.1 재조정 할당자의 개념

당신이 malloc을 써서 한 블럭을 할당하고 나면, 당신이 그 크기를 변경할 목적으로 realloc을 사용하지 않는 이상 그 블럭의 주소는 결코 변하지 않는다. 그러므로, 당신은 당신이 원하는 대로 다양한 공간에서 그 주소를 일시적으로 또는 영구적으로 안전하게 보관할 수가 있다. 그러나 당신이 재조정 할당자를 사용하려 할 때는 안전할 수가 없다. 왜냐하면, 당신이 어떤 방식으로든 메모리를 할당하려 할 때마다 모든 재조정 가능한 블럭들이 움직일 수 있기 때문이다. malloc이나 realloc을 호출하는 일조차도 재조정 가능한 블럭들을 움직일 수 있다. 각각의 재조정가능 블럭들에 대해서 당신은 그 블럭의 주소가 저장될 수 있도록 메모리 상에 핸들_포인터 지정을 만들어야 한다. 재조정 할당자는 각 블럭의 핸들의 위치를 알고 있어서 블럭을 옮길 때마다 그곳에 저장된 주소를 갱신하며, 그 결과로 핸들은 언제나 그 블럭을 가리키게 된다. 당신이 블럭의 내용에 접근하고자 할 때마다, 핸들에서 그 블럭의 갱신된 주소를 가져와야만 한다.

부호취급자로부터 재조정 할당자 함수들을 호출하는 것은 거의 분명히 부정확하다. 왜냐하면 그 부호는 언제든지 변할 수 있고 모든 블럭을 재조정할 수 있기 때문이다. 이것을 안전하게 할 수 있는 유일한 방법은 재조정 가능한 어떤 블럭의 내용도 그 부호에 접근하지 못하도록 봉쇄하는 것이다. 21.4.6 [재진입금지] 참조.

 

3.6.2 재조정 가능한 블럭의 할당과 해제

아래의 서술에서,handleptr이 핸들의 주소를 가리킨다. 모든 함수들은 'malloc.h'에 선언되어 있다. 모든 것은 GNU의 확장이다.

함수 void * r__alloc (void **handleptr, size_t size)

이 함수는 재조정 가능한 size 크기의 블럭을 할당한다. 이 함수는 그 블럭의 주소를 *handleptr에 담고 성공했을 때 널(null)이 아닌 문자를 반환한다. 만약 r_alloc이 필요한 공간을 얻지 못하면 *handleptr에 널 포인터를 저장하고 나서 널 포인터를 반환한다.

함수 void r__alloc__free (void **handleptr)

이 함수는 재조정 가능한 블럭을 해제하는 용도로 쓰인다. 이 함수는 *handleptr이 가리키는 블럭을 해제하고 더이상 어떤 할당된 블럭도 가리키고 있지 않음을 표시하기 위하여 *handleptr에 널 포인터를 담아둔다.

함수 void * r__re__alloc (void **handleptr, size_t size)

함수 r_re_alloc은 *handleptr이 가리키는 블럭의 크기를 조정하는데 size바이트만큼 크게 한다. 이 함수는 *handleptr에 크기가 변한 블럭의 주소를 저장하고 성공을 하게되면 널이 아닌 포인터를 반환한다. 만약 충분한 메모리를 사용할 수 없게 되면, 이 함수는 널 포인터를 반환하고 *handleptr을 변경하지 않는다.


3.7 메모리 사용 경고

당신은 memory_warnings를 호출함으로써 프로그램이 메모리 공간을 다 써버리지 않도록 경고를 요청할 수 있다. 이 함수는 운영체제로부터 더 많은 메모리를 요청할 때마다 메모리 사용을 체크하도록 malloc에게 알려준다. 이것은 'malloc.h'에 선언되어 있는 GNU의 확장이다.

void memory__warnings (void *start, void (*warn_func) (const char function *))

실제적인 메모리가 고갈되어가고 있음을 경고하려할 때 이 함수를 호출하라. 인수 start는 메모리 상에서 데이터 공간이 시작되는 곳을 말한다. 할당자는 이것을 사용된 마지막 주소와 대조하고 또 데이터 공간의 한계와 대조하여, 사용 가능한 메모리의 단편을 사용토록 결정한다. 만약 당신이 start에 0을 대입하게 되면 대부분의 환경에서 쓰일 수 있는 내정 값이 사용되게 된다. 경고함수를 사용할 때 malloc이 당신에게 경고를 주기 위해 호출할 수 있는 함수를 사용하라. 그것은 인수로서 문자열(경고 메시지)을 동반하여 호출된다. 보통 그 문자열은 사용자가 읽을 수 있게끔 디스플레이 된다. 경고는 메모리의 75%, 85%, 95%를 사용했을 때 주어진다. 95%가 넘었을 때는 메모리가 쓰일 때마다 경고를 얻을 수 있다.