GNU 체제는 프로그램 상에서 분명하게 통제하는 방식으로 메모리를 할당하는 몇 가지 방법을 제공한다. 그것들은 일반성과 유효성의 범위 내에서 변화한다.
malloc 함수는 일반적인 동적 메모리를 충분히 확보한다. 3.3 [제한 없는 할당] 참조.
다른 함수로는 obstack이 있는데, 이것은 malloc보다는 덜 일반적이지만 스택 할당에서는 훨씬 더 편리하고 유용하다. 3.4 [Obstacks] 참조.
함수 alloca는 저장소를 동적으로 할당하고 자동적으로 해제해 준다. 3.5 [자동적인 크기 변화] 참조.
동적 메모리 할당이란 프로그램이 어떤 정보를 어디에 저장해놓고 실행될 것인가를 결정하는 기법이다. 동적 메모리는 당신이 작업하는 데이터가 메모리 블록을 몇 개나 필요로 하는지 또는 그 메모리들을 얼마동안 사용할 것인지에 따라 요구된다.
예를 들면, 당신이 입력파일에서 읽어들인 한 개의 라인을 저장하려면 하나의 블럭을 필요로 한다. 그런데, 한 개의 라인이 어느 정도의 길이로 되어야 하는지가 제한이 없으므로, 당신은 저장소를 동적으로 할당할 수밖에 없으며, 그것을 당신이 더 큰 라인을 읽어들였을 때보다도 더 크게 할당해야만 한다. 또는, 당신이 입력 데이터에서 만든 각 레코드나 정의를 위해서 한 블럭을 필요로 하기도 한다. 그런데, 당신이 사전에 그것이 얼마나 많을지를 모를 수 있으므로, 각각의 레코드나 정의가 읽혀질 때마다 새로운 블럭을 할당해야만 한다. 당신이 동적 메모리를 할당할 때에는 메모리 한 블럭을 할당할 때마다 프로그램 상에서 분명하게 표현하여야 한다. 당신은 공간을 할당하고 싶을 때마다 함수나 매크로를 호출하여야하며, 인수를 가지고 그 크기를 확정해야 한다. 당신이 그 공간을 해제하고 싶을 때에도, 다른 함수나 매크로를 호출하여 그렇게 처리하면 된다. 당신은 이러한 작업을 원할 때마다 원하는 만큼 할 수 있는 것이다.
C언어는 C프로그램에 있는 변수들을 통해 메모리 할당을 위한 두 가지 종류를 지원한다.
정적할당은 당신이 정적변수를 정의할 때 발생되는 것이다. 각 정적변수는 정해진 크기의, 메모리 한 블록을 정의한다. 그 공간은 당신의 프로그램이 시작되어 일단 할당되면 절대 해제되지 않는다.
자동 할당은 함수의 인자나 지역변수와 같은 자동변수를 정의할 때 발생한다. 자동변수를 위한 공간은 그 정의를 포함한 곳(compound statement)에 들어가면 할당되고 나오면 해제된다. GNU C에서 자동저장의 길이가 다양하게 표현되어질 수 있지만 다른 C에서는 상수이어야만 한다.
동적 할당은 C변수들에 의해 지원되지 않는다; C에는 "다이내믹"이라는 스토리지 클래스가 없고 동적으로 할당된 공간에 저장된 값은 결코 C의 변수를 통해 할 수 없다. 오직 포인터를 통해 동적으로 할당된 공간을 참조하는 것이 유일한 방법이다. 그래서 좀 불편한데다, 동적 공간을 통해 활동하는 프로세스는 실행시간이 좀 더 필요하기 때문에 프로그래머들은 정적이나 자동변수를 통해 할 수 없을 경우에만 이 동적 할당을 사용한다.
예를 들어 만약 당신이 구조체 foobar를 위해 어떤 공간을 동적으로 할당하기 원한다면 당신은 단순히 struct foobar라고 만 변수를 선언할 수 없다. 그러나 struct foobar * 의형으로 포인터 변수를 선언하면 그 공간의 주소를 지정할 수 있다. 그러면 당신은 *와 -> 오퍼레이터를 사용해서 그 공간의 내용을 참조할 수 있다.
가장 일반적인 동적 할당을 위한 도구는 malloc인데 그것은 어느 크기의 메모리 블록도 언제든지 할당하는걸 허용하고 또한 그들을 언제든지 크게 하거나 작게 하거나 할 수 있고 언제든지 그 불럭들을 개별적으로 메모리 공간을 해제하는 것도 가능하다.
메모리 공간을 할당하기 위해 부르는 malloc의 프로토타입은 'stdlib.h' 이다.
당신은 캐스트(case)하지 않고 어느 포인터 변수 안에도 malloc의 결과를 저장할 수 있는데 그것은 ANSI C는 자동적으로 필요할 때 void형 포인터를 다른 어떤 형으로도 변환시키기 때문이다. 그러나 캐스트(case)는 지정명령어를 명시하거나 당신이 전통적인 C로 코딩을 원한다면 필요하다. 스트링을 위한 메모리를 할당할 때 malloc의 인수에 스트링의 길이+1을 하는 것을 잊지 마라. 이것은 필요한 공간임에도 불구하고 스트링의 길이에 포함되지 않는 널 문자로 끝나기 때문이다.
/* 보충하면 스트링의 맨 끝에 있는 널 문자를 스트링의 문자열이 끝나는 곳임을 나타내기 위해서 중요한 공간이지만 스트링의 길이를 셀 때는 포함되지 않으므로 스트링을 위해 메모리를 할당할 때는 그 널 문자를 위해 스트링의 길이 +1 만큼의 메모리를 할당받자란 얘기일걸요.*/
malloc은 원하는 메모리 블록보다 메모리 공간이 적은 경우에 널을 반환한다. 당신은 모든malloc의 값을 체크해볼 수 있기 때문에 malloc을 부르고 그 값이 널 포인터라면 에러를 사용자에게 보고할 수 있고 아니면 그 값을 오직 반환 하는 서브루틴을 쓰고자 할 때 유용하다. 이 함수는 상투적으로 xmalloc라고 부른다. 여기에 그것이 있다:
여기에 malloc의 실제 사용 예가 있다. savestring이라는 함수는 널 문자로 끝나는 새로운 할당 공간에 문자들의 열을 복사하는 함수다.
malloc으로 할당된 메모리 블록은 당신에게 어떤 데이터 타입도 저장할 수 있고 그것과 일치시킴을 보증한다. GNU시스템에서 주소는 항상 8의 배수로 증가한다. 그러나 블록이 16개보다 많은 경우에 주소는 항상 16의 배수이다. 오직 드물게 높은 경계가 필요한 경우에는 memalign이나 valloc을 사용한다. ( 3.3.7절의 [Aligned Memory Blocks]를 보라)
무언가를 위해 사용되어진 블록의 끝에 할당된 메모리는 아마도 다른 블록으로 이미 다른 malloc에 의해 할당되어졌을 것이다. 만약 이미 당신이 요구한 것보다 더 많은 메모리 블록을 취급하길 원한다면 자칫하면 내 자신의 블록의 데이터를 파괴하거나 아니면 다른 블록의 내용을 파괴할지도 모른다. 만약 당신이 이미 한 블록을 할당받았고 거기에 더 큰공간이 필요하다면 realloc을 사용하라
당신이 malloc으로 얻었던 메모리블럭을 더 이상 사용하지 않으면 free함수를 사용하여 어떤 블록이 그 공간을 다시 할당받을 수 있도록 메모리를 해제해야 한다. free함수의 프로토타입은 'stdlib.h'이다.
void free (void *ptr)
void cfree (void *ptr)
가끔, free함수는 운영체제에게 실제로 메모리를 반환할 수 있고 프로세스를 작게 만들지만 보통은 그 공간의 재사용을 위해 malloc에 의해 다시 불려진 후에 허용된다. 즉, 그 공간(이미 해제된)은 해제된 메모리 리스트를 당신의 프로그램 안에 남겨 다른 malloc에 의해 내부에서 사용된다. 프로그램의 끝에서 블록을 해제할 수 없는데 그것은 프로그램에서 쓰인 모든 공간을 그 프로세스가 끝날 때 시스템에게 되돌려 주어야 하기 때문이다.
함수 calloc은 메모리를 할당하고 거기에 0을 채운다. 이 함수는 'stdlib.h'에 선언되어 있다.
함수 void * calloc (size_t count, size_t eltsize)
요즘은 calloc을 자주 쓰지 않는다, 왜냐하면 더 자주 쓰이는 다른 함수들을 간단히 결합해서 써도 되기 때문이다. calloc은 아주 쓸 모 없지는 않지만 구시대의 유물이 되어가고 있다.
malloc을 가장 잘 사용하려면, malloc의 GNU 버전은 항상 적은 양의 메모리를 그 크기가 2의 배수인 블럭으로 나누는 것임을 알아야 한다. 그것은 분할된 영역을 2의 배수로 유지한다. 이것은 그 크기를 한 의 크기로 만들 수 있게 한다. 그러므로, 만약 당신이 malloc을 효율적으로 만들기 위해서 작은 블럭의 크기를 선택할 수 있는 경우라면 그것을 2의 배수로 만들어라.
일단 한 가 특정한 블럭의 크기로 나누어지게 되면, 그것은 모든 블럭이 해제되지 않는 이상 다른 크기로 재 사용될 수는 없다. 많은 프로그램에서 블럭이 다른 크기로 재 사용되는 일은 좀처럼 일어나지 않는다. 따라서, 많은 다른 목적으로 사용할 블럭들을 같은 크기로 나누어 놓음으로써 프로그램이 메모리를 효율적으로 사용토록 할 수가 있는 것이다.
한 또는 그 이상의 메모리 블럭들을 요구할 때, malloc은 다른 방법을 쓴다; 그 크기를 한 의 배수로 만들고, 그 블럭을 통합하거나 필요한 만큼 나누어서 사용할 수 있다.
두 가지의 방법을 쓰는 이유는 작은 블럭들을 가능한 한 빨리 할당하고 해제하는 것이 중요하다는 데 있다. 그러나 큰 블럭의 경우에는 프로그램이 그것을 사용하는 데 어느 정도의 시간을 소요하기 때문에 속도가 그리 중요치 않다. 또한 큰 블럭은 당연히 그 숫자에 있어서 적은 것이다. 그러므로, 큰 블럭에 있어서는 낭비된 공간을 최소화하는 데 시간을 좀더 투자하는 방법이 의미가 있는 법이다.
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 )
함수 void * valloc (size_t size)
당신은 mcheck 함수를 사용하여 동적 저장소의 일관성을 체크하는 데 malloc을 요청할 수 있다. 이 함수는 GNU 확장 함수이며,'malloc.h'에 선언되어 있다.
함수 int mcheck (void (*abortfn) (void))
GNU C 라이브러리는 적절한 hook 함수들을 명시함으로써 당신이 malloc,realloc과 free의 행위를 변경할 수 있도록 해준다. 이러한 hook들은, 예컨대, 동적 저장소 할당을 사용한 프로그램들을 디버그 하는데 도움을 준다. hook 변수들은 'malloc.h'에 선언되어 있다.
변수 ____malloc__hook
변수 ____realloc__hook
변수 ____free__hook
당신은 이 함수들 중의 하나의 hook로써 당신이 인스톨한 함수는 그 hook의 이전의 값이 먼저 저장되지 않은 상태에서 재차 그 함수를 호출하지는 않는다는 점을 분명하게 알아두어야 한다! 그렇지 않으면 당신의 프로그램은 무한반복에 빠져 버릴 것이다.
이제 __malloc_hook을 적절하게 사용하는 법을 예를 들어보기로 하자. 그것은 malloc이 호출될 때마다 정보를 기록해내는 함수를 인스톨한다.
mcheck 함수(3.3.8 [메모리 heep 일정성 체크하기] 참조)는 이러한 hook들을 인스톨하여야 작동한다.
당신은 mstats 함수를 호출함으로써 동적 저장소 할당에 관한 정보를 얻을 수 있다. 이 함수와 그것의 연관된 데이터 형태는 'malloc.h'에 선언되어 있다. 그것들은 GNU의 확장이다.
데이터 형태 struct mstats
size_t bytes_total
size_t chunks_used
size_t bytes_used
size_t chunks_free
size_t bytes_free
함수 struct mstats mstats (void)
malloc과 함께 작동하는 함수들을 요약해보자.
void *malloc (size_t size)
void free (void *addr)
void *realloc (void *addr, size_t size)
void *calloc (size_t count, size_t eltsize)
void *valloc (size_t size)
void *memalign (size_t size, size_t boundary )
int mcheck (void (*abortfn) (void))
void *(*__malloc_hook) (size_t size)
void *(*__realloc_hook) (void *ptr, size_t size)
void (*__free_hook) (void *ptr)
struct mstats mstats (void)
obstack은 object들의 스택들이 있는 메모리의 공동관리소(pool) 이다. 당신은 obstack들을 분리하여 몇 개라도 만들 수 있고 그리고 정해진 obstack에 object를 할당한다. 각 obstack안에 마지막 objedt에서 첫 번째 하나는 반드시 해제된 상태 이여야 하지만 개별적 obstack들은 다른 것에 독립적이다.
해제에 관한 하나의 제약 말고는 obstack들은 전체적으로 일반적이다; obstack은 어느 크기의 object들을 몇 개라도 저장할 수 있다. 그들은 매크로로 실행되고 그런 할당은 object가 보통 작아서, 매우 빠르다. 그리고 적당한 경계 위에 각 object를 시작하기 위해 필요한 공간이 있는데 그것은 각 대상마다 덧붙여 있다.
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을 사용하는 함수를 공급한다. 당신은 청크를 해제하기 위해서도 함수를 공급해야만 한다. 이 문제들은 다음절에 설명되어 있다.
당신이 obstack을 사용하려고 계획했다면 각 소스파일 안에 'obstack.h'라는 헤더파일을 포함해야만 한다. 이것은 다음과 같다.
또한 소스파일에 매크로 obstack_init를 사용한다면 obstack 라이브러리에 의해 불려질 매크로나 두 개의 함수를 선언하거나 정의해야만 한다. 하나는 obstack_chunk_alloc으로 모아져있는( are packed) object들에 메모리의 청크를 할당하기 위해 사용되는 것이다. 다른 하나는obstack_chunk_free로 청크에서 object들을 해제하고 청크를 반환하는데 사용한다.
보통 이들은 xmalloc 이라는 매개자를 거쳐 malloc을 사용하도록 정의되어 있다. ( 3.3장[Unconstrained Allocation] 를 참조) 이것은 밑에 있는 두 개의 매크로 정의로 한다.
당신이 obstack들을 사용하면서 얻어낸 저장공간은 실제로 malloc을 통해 할당된 것이고, obstak를 사용하는 건 메모리의 큰 블록들은 덜 자주 호출하기 때문에 좀더 빠르다.
실행시 프로그램이 obstack으로 struct obstack타입인 object를 사용하기 전에 obstack_init를 호출해서 obstack을 초기화해야만 한다.
함수 void obstack__init (struct obstack *obstack_ptr)
여기에 obstack을 위해 어떻게 공간을 할당하고 그것을 초기화하는 두 가지 예가 있다.
obstack에 object를 할당하기 위한 가장 직접적인 방법을 obstack_alloc을 사용하는 건데 이것은 거의 malloc과 같다.
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안에 할당된 object를 해제하기 위해서는 obstack_free함수를 사용한다. object들의 스택인 obstack은 한 object가 해제되면 그 object이후 최근에 같은 obstack에 할당된 다른 object들도 자동적으로 해제시킨다.
함수 obstack__free (struct obstack *obstack_ptr, void *object)
obstack들의 사용을 위한 인터페이스는 컴파일러에 의존하는 함수나 매크로로 정의되어 진다. 모든 C 컴파일러는 ANSI C와 전통적인 C를 지원하는 obstack 도구를 가지고 있다. 하지만 당신이 GNU C 보다는 다른 컴파일러를 사용하려 한다면 많은 주의를 해야한다.
만약 당신이 오래된 비 ANSI C 컴파일러를 사용하면 모든 obstack 함수들은 매크로로 정의되어 있다. 당신은 이 매크로를 함수들처럼 호출 할 수 있지만 당신은 그것을 다른 방법으로는 사용할 수 없다.( 예를 들어, 그들의 주소를 줄 수 없다. )
매크로의 호출은 특별한 주의가 요구된다.; 즉, 첫 번째 피연산자 ( obstack 포인터)가 부작용을 포함하고 있다고 할 수 있을지도 모르는데, 그것은 그것이 여러 번 연산되기 때문이다. 예를 들어 만약 당신이 아래처럼 쓰면;
당신은 여러 번 get_obstack이 호출되는걸 발견할 수 있다. 만약 당신이 obstack 포인터 인자처럼 *obstack_list_prt++을 사용하면 여러 번 증가가 발생하여 이상한 결과를 얻을 것이다.
ANSI C는 함수와 매크로 정의 둘을 다 지원한다. 함수 정의는 그것을 호출하지 않고 함수의 주소로 사용되는 것이다. 보통의 호출은 암묵적으로 매크로 정의가 사용되지만 당신은 괄호 안에 함수의 이름을 씀으로써 함수정의를 요청할 수 있다. 아래처럼;
이것은 표준 라이브러리 함수를 위한 ANSI C안에 있는 것과 같다. 1.3.2절[Macro Definitions] 를 보라.
주의 : 심지어 ANSI C에서 조차도 첫 번째 피연산자의 부작용을 피하는데 주의를 기울여야 한다. 만약 당신이 GNU C컴파일러를 사용하고 있다면 이 주의는 필요하지 않는데, 그것은 GNU C안에 다양한 언어의 확장은 오직 한번 각 인수를 실행하도록 그 매크로를 정의하는 것을 허용한다.
obstack 덩어리 내의 저장소는 연속적으로 사용되기 때문에 어떤 대상물을 차례로 쌓아서 대상물의 끝에 한번에 하나 또는 그 이상의 바이트를 추가할 수 있다. 이 기법을 사용함에 있어 당신은 그 대상물의 끝에 다다를 때까지 그 대상물에 얼마만큼의 데이터를 넣을 수 있는지를 알 필요가 없다. 이것은 성장하는 대상물 기법이라 불린다. 성장하는 대상물에 데이터를 추가하는 특수한 함수들은 이 절에 기술되어 있다. 당신은 어떤 대상물을 성장시키기 시작할 때 아무런 특별한 조치도 할 필요가 없다. 그 대상물에 데이터를 추가시키는 함수들 중의 하나를 사용하게 되면 그것은 자동적으로 시작된다. 그러나 그 대상물이 끝날 때에는 분명하게 말해 줄 필요가 있다. 이것은 함수 obstack_finish에 의해 수행된다. 이렇게 해서 만들어진 대상물의 실제 주소는 그것이 끝나기 전에는 알 수가 없다. 그때까지는 항상 대상물이 새로운 덩어리 속으로 복사되어야 할만큼의 데이터만이 추가될 수 있을 뿐이다. obstack이 성장하는 대상물을 위해 사용되고 있을 동안에는 당신은 다른 대상물을 할당하기 위해서 그것을 사용할 수는 없다. 만약 그렇게 한다면, 이미 성장하는 대상물에 추가된 공간이 다른 대상물의 일부분으로 되어버릴 것이다.
함수 void obstack__blank (struct obstack *obstack_ptr, size_t size)
함수 void obstack__grow (struct obstack *obstack_ptr, void *data, size_t size)
함수 void obstack__grow0 (struct obstack *obstack_ptr, void *data, size_t size)
함수 void obstack__1grow (struct obstack *obstack_ptr, char c)
함수 void * obstack__finish (struct obstack *obstack_ptr)
함수 size_t obstack__object__size (struct obstack *obstack_ptr)
성장하는 대상물을 만드는 통상적인 함수들은 현재의 메모리 덩어리에 새로운 성장을 위한 공간이 있는지를 체크하기 위하여 부가물을 초래한다. 만약 당신이 대상물을 조금씩 단계별로 만들고자 한다면 이 부가물은 중요한 것이 될 수 있다.
당신은 체크하지 않고 대상물을 성장시키는 특수한 "급속한 성장" 함수를 사용함으로써 부가물을 줄일 수 있다. 견실한 프로그램을 만들기 위해서는 당신 자신이 체크를 수행해야만 한다. 만약 당신이 대상물에 데이터를 추가하려 할 때마다 가장 간단한 방법으로 이러한 체크를 수행하려 한다면, 당신은 아무 것도 저장하지 않아도 통상적인 성장 함수들이 이를 행할 것이다. 그러나 당신이 체크의 횟수를 줄이되 더 효과적으로 체크할 수 있도록 배열한다면, 당신은 프로그램을 빠르게 만드는 것이 된다. 함수 obstack_room은 현재의 메모리 덩어리에서 사용 가능한 공간의 양을 반환한다. 그것은 다음과 같이 선언된다:
size_t obstack__room (struct obstack *obstack_ptr)
void obstack__1grow__fast (struct obstack *obstack_ptr, char c)
함수 void obstack__blank__fast (struct obstack *obstack_ptr, size_t size)
하나의 obstack에서 현재의 할당 위상에 관한 정보를 제공하는 함수들이 있다. 당신은 성장하고 있는 대상물에 대해서 알아보기 위해서 그것들을 사용할 수 있다.
함수 void * obstack__base (struct obstack *obstack_ptr)
함수 void * obstack__next__free (struct obstack *obstack_ptr)
함수 size_t obstack__object__size (struct obstack *obstack_ptr)
따라서, 위의 스택에 담긴 대상물의 크기는, "시작점(해제지점) - 종료지점"이 되는 건가? 음...,스택과 그 주소를 잘 이해하고 있는 고수들만이 제대로 이해할 수 있겠군...
각 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가 즉각 효과를 갖도록 할 수 있다. 이것은 길이가 제로인 대상물을 종료하고 다음 대상물을 위한 적절한 할당을 하는 것이다.
obstack은 자체의 기능으로 큰 덩어리에 공간을 할당하고 당신의 요청에 부응하여 덩어리에서 공간을 분배한다. 덩어리들은 당신이 별도의 크기를 지정하지 않는 이상 4096 바이트의 크기이다. 덩어리 크기는 대상물들을 저장하는 데에 실제로 사용되지 않는 8 바이트의 부가물을 포함한다. 설정된 크기에 관계없이, 큰 대상물을 위해서 필요하다면 더 큰 덩어리들이 할당되어질 것이다.
obstack 라이브러리는 함수 obstack_chunk_alloc을 호출하여 덩어리들을 할당하는데, 당신이 그것을 정의하여야 한다. 당신이 어떤 덩어리에 있는 대상물을 해제한 결과로서 어떤 덩어리가 더이상 필요치 않게 되면, obstack 라이브러리는 함수 obstack_chunk_free를 호출하여 그 덩어리를 해제한다.
물론 당신은 그것 또한 정의하여야 한다. 이들 둘은 obstack_init(3.4.1 [obstack 만들기] 참조) 을 사용하는 각 소스파일에서 (매크로로)정의되거나 (함수로)선언되어야만 한다. 그것들은 거의 대부분 아래와 같이 매크로로 정의된다:
이들은 (인수가 아니라) 단순히 매크로임을 주의하라. 인수를 갖는 매크로 정의는 유효치 않다! 다만 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과 관련된 함수들을 요약해보자. 각 함수들은 첫 인수로서 obstack(구조체 포인터로서)의 주소를 갖는다.
void obstack_init (struct obstack *obstack_ptr)
void *obstack_alloc (struct obstack *obstack_ptr, size_t size)
void *obstack_copy (struct obstack *obstack_ptr, void *address, size_t size)
void *obstack_copy0 (struct obstack *obstack_ptr, void *address, size_t size)
void obstack_free (struct obstack *obstack_ptr, void *object)
void obstack_blank (struct obstack *obstack_ptr, size_t size)
void obstack_grow (struct obstack *obstack_ptr, void *address, size_t size)
void obstack_grow0 (struct obstack *obstack_ptr, void *address, size_t size)
void obstack_1grow (struct obstack *obstack_ptr, char data_char)
void *obstack_finish (struct obstack *obstack_ptr)
size_t obstack_object_size (struct obstack *obstack_ptr)
void obstack_blank_fast (struct obstack *obstack_ptr, size_t size)
void obstack_1grow_fast (struct obstack *obstack_ptr, char data_char)
size_t obstack_room (struct obstack *obstack_ptr)
int obstack_alignment_mask (struct obstack *obstack_ptr)
size_t obstack_chunk_size (struct obstack *obstack_ptr)
void *obstack_base (struct obstack *obstack_ptr)
void *obstack_next_free (struct obstack *obstack_ptr)
불충분한 동적 할당을 지원하는 함수들 중의 하나인 alloca는 동적으로 할당을 하지만 자동적으로 해제되지는 않는다. alloca를 가지고 블록을 할당하는 것은 명백한 동작이다; 당신은 원하는 만큼의 많은 블록을 할당할 수 있고 실행 시에 그 크기를 계산할 수도 있다. alloca로 할당된 공간의 변수가 단지 그 함수 안에서 자동변수라고만 선언이 되었다면 그렇게 할당된 블록들은 함수를 빠져나갈 때 모두 해제된다. 하지만 다른 경우는 명백하게 그 공간을 해제하는 방법이 없다. alloca의 프로토타입은 'stdlib.h'이다. 이 함수는 BSD확장이다.
함수 void * alloca (size_t size);
alloca의 사용 예로서 여기에 한 함수가 있다. 이 함수는 인수로 받은 두 개의 문자열을 붙여서 만든 것을 파일이름으로 해서 그 파일을 열고 그것을 파일기술자에 전달하거나 실패한 경우는 -1을 반환 하는 함수이다.
위에서 본 것처럼 alloca를 이용하면 더 간단하게 구현할 수 있겠지만 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를 바꾸어보자.
좀더 자세히 제가 이해한대로 보충설명을 드리자면... 실제로 open_or_report_error 이라는 함수가 존재하지 않는데 그냥 있다고 가정하고 쓰면 프로그램 내에서 그것을 호출할 수 없으므로 에러가 나겠지요. 그런데 그 alloca란 놈이 일단 에러가 나면 alloca를 통해 할당된 모든 공간을 해제하고 빠져 나올 수 있기 때문에, 그리고 alloca는 명백하게 공간은 해제해주는 기능이 없으니까.. 이런 편법을 사용하게 되나봐요.
alloca를 통한 작업에서 이 방법을 사용하는 이유는 alloca를 통해 할당된 공간은 에러가 발생하면 어떤 특별한 노력 없이 할당된 공간을 해제할 수 있기 때문이다. 앞의 open2와 비교해서( malloc과 free를 사용하는 ) 이 방법을 사용하면 저장공간의 유출을 발견할 수 있을 것이다. 그러나 당신이 좀더 많은 변화를 원한다면 그렇게 하는 쉬운 방법은 아무 것도 없다.
이곳은 malloc과 비교하여 alloca의 단점을 보여준다. 만약 당신이 alloca를 가지고 시스템이 제공할 수 있는 것보다 더 많은 저장공간을 할당받길 원하면 당신은 명백한 에러 메시지를 얻을 수 없다. 대신에 당신은 무한 재귀호출에서 얻을 수 있는 것과 같은 심각한 신호를 얻을 것이다.; 아마도 segmentation 위배 ( 21.2.1절[ 프로그램 에러 신호] 참조) 어떤 비GNU 시스템들은 이 alloca를 지원하지 않아서 이식성의 측면에서는 좀 떨어진다. 그렇지만 alloca는 이런 결점에도 불구하고 유용하게 사용된다.
GNU C에서 당신은 alloca의 사용을 변화 가능한 크기를 갖는 배열을(an array of variable size) 사용해서 대체할 수 있다. 여기에 그것을 보여주는 예가 있다.
그러나 alloca는 여러 가지 이유로 변화 가능한 크기를 갖는 배열로 항상 대체할 수 있는 것은 아니다. 변화 가능한 배열에게 할당된 메모리 공간은 배열이 선언된 영역의 끝에서 해제되지만 alloca로 할당된 공간은 함수가 끝날 때까지 남아 있다.
반복을 사용했을 때 각 반복마다 더해서 블록을 할당하는 loop에서 alloca를 사용하는 것은 가능하다. 하지만 변화 가능한 크기를 갖는 배열로는 불가능하다.
주의 : 만약 당신dl alloca와 변화 가능한 크기를 갖는 배열을 한 함수 안에서 혼용하면, 변화 가능한 크기를 갖는 배열이 선언된 영역을 벗어날 때 그 영역이 실행되는 동안에 alloca로 할당했던 모든 블록들도 해제된다는 것을 명심하라.
어떠한 체제의 동적 메모리 할당이든 간에 부가물을 갖는다: 그것이 사용하는 공간의 양은 프로그램이 요구하는 공간의 양보다 크다. 메모리 재조정 할당자는 독자적 판단으로 필요한 만큼의 메모리 블럭을 이동시킴으로써 부가물을 최소화한다.
당신이 malloc을 써서 한 블럭을 할당하고 나면, 당신이 그 크기를 변경할 목적으로 realloc을 사용하지 않는 이상 그 블럭의 주소는 결코 변하지 않는다. 그러므로, 당신은 당신이 원하는 대로 다양한 공간에서 그 주소를 일시적으로 또는 영구적으로 안전하게 보관할 수가 있다. 그러나 당신이 재조정 할당자를 사용하려 할 때는 안전할 수가 없다. 왜냐하면, 당신이 어떤 방식으로든 메모리를 할당하려 할 때마다 모든 재조정 가능한 블럭들이 움직일 수 있기 때문이다. malloc이나 realloc을 호출하는 일조차도 재조정 가능한 블럭들을 움직일 수 있다. 각각의 재조정가능 블럭들에 대해서 당신은 그 블럭의 주소가 저장될 수 있도록 메모리 상에 핸들_포인터 지정을 만들어야 한다. 재조정 할당자는 각 블럭의 핸들의 위치를 알고 있어서 블럭을 옮길 때마다 그곳에 저장된 주소를 갱신하며, 그 결과로 핸들은 언제나 그 블럭을 가리키게 된다. 당신이 블럭의 내용에 접근하고자 할 때마다, 핸들에서 그 블럭의 갱신된 주소를 가져와야만 한다.
부호취급자로부터 재조정 할당자 함수들을 호출하는 것은 거의 분명히 부정확하다. 왜냐하면 그 부호는 언제든지 변할 수 있고 모든 블럭을 재조정할 수 있기 때문이다. 이것을 안전하게 할 수 있는 유일한 방법은 재조정 가능한 어떤 블럭의 내용도 그 부호에 접근하지 못하도록 봉쇄하는 것이다. 21.4.6 [재진입금지] 참조.
아래의 서술에서,handleptr이 핸들의 주소를 가리킨다. 모든 함수들은 'malloc.h'에 선언되어 있다. 모든 것은 GNU의 확장이다.
함수 void * r__alloc (void **handleptr, size_t size)
함수 void r__alloc__free (void **handleptr)
함수 void * r__re__alloc (void **handleptr, size_t size)
당신은 memory_warnings를 호출함으로써 프로그램이 메모리 공간을 다 써버리지 않도록 경고를 요청할 수 있다. 이 함수는 운영체제로부터 더 많은 메모리를 요청할 때마다 메모리 사용을 체크하도록 malloc에게 알려준다. 이것은 'malloc.h'에 선언되어 있는 GNU의 확장이다.
void memory__warnings (void *start, void (*warn_func) (const char function *))