10-3-라.재할당

다음 함수(씨얼록이라고 읽는다)는 malloc 함수와 마찬가지로 메모리를 할당하되 필요한 메모리양을 지정하는 방법만 다르다.

 

void *calloc( size_t num, size_t size );

 

첫 번째 인수 num은 할당할 요소의 개수이고 size는 요소의 크기이다. malloc은 필요한 메모리를 바이트 단위 하나로만 전달받지만 calloc은 두 개의 값으로 나누어 전달받는다는 점이 다르다. malloc이 "몇 바이트 할당해 주세요"라고 요청하는 것에 비해 calloc은 "몇 바이트짜리 몇 개 할당해 주세요"라고 요청하는 것이다. 그래서 다음 두 호출문은 동일하다.

 

ar=(int *)malloc(10*sizeof(int));

ar=(int *)calloc(10,sizeof(int));

 

구조체같은 큰 데이터의 배열을 할당할 때는 calloc으로 할당하는 것이 더 보기에 좋고 코드를 읽기에도 좋다. calloc도 실제 할당하는 양은 size*num으로 계산하므로 사실 calloc(1,10*sizeof(int))나 calloc(10*sizeof(int),1)이나 결과는 동일하다. 다만 필요한 메모리양을 단위와 개수로 나누어 좀 더 논리적으로 표현한다는 점만 다르다.

calloc이 malloc과 또 다른 차이점은 메모리 할당 후 전부 0으로 초기화한다는 것이다. malloc은 메모리 할당만 하므로 할당된 메모리에는 쓰레기값이 들어 있지만 calloc으로 할당하면 할당 후 모든 메모리를 0으로 채운다. 할당 후에 배열을 바로 초기화해야 한다면 malloc 호출 후 memset을 사용할 수도 있지만 이 방법보다는 calloc 함수로 할당하는 것이 더 편리하다

다음 함수는 이미 할당된 메모리의 크기를 바꾸어 재할당한다. 최초 할당한 크기보다 더 큰 메모리가 필요할 때는 이 함수로 크기를 조정할 수 있다. 원래 크기보다 더 작게 축소 재할당하는 것도 가능하기는 하지만 보통은 확대 재할당하는 경우가 많다.

 

void *realloc( void *memblock, size_t size );

 

첫 번째 인수로 malloc이나 calloc으로 할당한 메모리의 시작 번지를 주고 두 번째 인수로 재할당할 크기를 전달한다. 만약 첫 번째 인수가 NULL일 경우, 즉 할당되어 있지 않을 경우는 새로 메모리를 할당하므로 realloc의 동작은 malloc과 같아진다. 두 번째 인수 size가 0일 경우는 할당을 취소하라는 얘기이므로 free와 같아진다. 재할당 후에 새로 할당된 메모리의 번지를 리턴하는데 이 번지는 원래 번지와 같을 수도 있고 아닐 수도 있다. 일반적으로 축소 재할당했을 때는 같은 번지이며 확대 재할당했을 때는 다른 번지로 이동될 확률이 높다.

예를 들어 ar 배열이 20바이트만큼 할당되어 있는 상태에서 40바이트로 확대 재할당한다고 해 보자. 만약 ar뒤쪽의 메모리가 비어 있다면 ar을 40바이트로 늘리기만 하면 되므로 ar은 그 자리에서 길이만 늘어난다. 그러나 ar다음의 메모리가 비어 있지 않다면 ar의 위치가 바뀌게 될 것이다.

ar 다음에 다른 변수가 이미 메모리를 차지하고 있으면 이 상태에서 ar 배열의 길이를 늘릴 수 없기 때문에 어쩔 수 없이 ar이 확대된 크기만큼의 여유 메모리가 있는 쪽으로 이사가야 한다. 이때 realloc 함수는 ar을 이동시키면서 기존 ar에 들어 있던 모든 내용을 그대로 복사하므로 재할당에 의해 위치는 바뀌더라도 내용은 그대로 유지된다. 다음 예제는 realloc 함수의 간단한 사용예이다.

 

: realloc

#include <Turboc.h>

 

void main()

{

     int *ar;

 

     ar=(int *)malloc(5*sizeof(int));

     ar[4]=1234;

 

     ar=(int *)realloc(ar,10*sizeof(int));

     ar[9]=5678;

 

     printf("ar[4]=%d, ar[9]=%d\n",ar[4],ar[9]);

     free(ar);

}

 

실행 결과는 다음과 같다.

 

ar[4]=1234, ar[9]=5678

 

최초 ar 배열을 20바이트 크기로 할당했으므로 ar은 ar[0]~ar[4]까지의 요소를 가지게 될 것이다. 이 상태에서 어떤 이유로 ar이 40바이트로 확장되어야 할 필요가 생겼다면 realloc 함수로 ar의 크기를 늘려 재할당한다. 크기를 확장하면 ar의 번지가 바뀔 수는 있지만 원래 배열에 들어 있던 모든 값은 그대로 유지된다.

동적 할당이 필요한 이유는 컴파일할 시점에 필요한 메모리양을 모를 때가 있기 때문이다. 재할당이 필요한 이유는 실행중에라도 필요한 메모리양을 가늠할 수 없을 때가 있기 때문이다. 그리 흔하지는 않지만 재할당이 꼭 필요한 경우가 있다. 어떤 경우에 재할당이 필요한지 구체적인 예제를 보이기는 어렵고 예만 들어 보도록 하자.

네트워크를 통해 파일을 전송하는 프로그램을 작성한다고 해보자. 네트워크의 반대편에서 보내는 파일을 받아야 하는데 이 파일의 크기는 다 받아 보기 전에는 알 수 없는 상황이다. 이럴 때는 최초 적당한 크기로 버퍼를 할당한다. 가령 1M 정도만 할당한 채로 네트워크로 들어오는 패킷을 이 버퍼에 누적시킨다. 그러다가 받은 패킷 총량이 1M가 되면 다시 1M 더 늘려 2M로 재할당한다. 이런 식으로 패킷을 다 받을 때까지 계속 재할당하면 된다. 이런 상황은 생각보다 훨씬 더 자주 발생하는데 압축을 해제한다거나 DB 쿼리를 실행할 때도 재할당이 필요하다.

참고로 다음 확장 함수를 사용하면 malloc, calloc으로 할당한 메모리의 크기를 실행중에 조사할 수 있다. 할당한 메모리가 충분한지를 조사하고 싶을 때 이 함수가 유용하게 사용된다. 표준 함수는 아니지만 비주얼 C++, Dev-C++ 등 웬만한 컴파일러들은 이 함수를 제공하므로 이식성에 대해서는 걱정하지 않아도 된다.

 

size_t  _msize(void *memblock);

 

메모리를 동적으로 할당하는 여러 함수들에 대해 알아보았는데 C 언어의 기본적인 메모리 할당, 해제 함수는 malloc, free이다. 이에 비해 C++은 malloc, free 대신 사용할 수 있는 new, delete라는 할당 연산자를 따로 제공하는데 이 연산자들은 단순히 메모리를 할당하기만 하는 것이 아니라 객체의 생성자와 파괴자를 호출하기도 한다. 이 연산자에 대해서는 C++ 편에서 다시 공부하기로 하자.

물론 C++에서도 객체를 생성할 때가 아니라면 malloc, free 함수를 여전히 사용할 수 있으며 new, delete보다 오히려 더 간편하다. 메모리 할당 함수는 이 외에도 운영체제와 라이브러리별로 다양하게 제공된다. 예를 들어 윈도우즈에서는 GlobalAlloc, VirtualAlloc이라는 별도의 할당 함수가 있고 COM에서는 IMalloc이라는 메모리 관리 전용 인터페이스를 제공하기도 한다. 이런 할당 방법에 대해서는 관련 과목을 공부할 때 다시 살펴보되 할당의 원칙과 방법은 malloc, free의 경우와 거의 동일하다.