10-3-다.할당 및 해제

메모리를 동적으로 할당 및 해제할 때는 다음 두 함수를 사용한다.

 

void *malloc(size_t size );

void free(void *memblock );

 

먼저 malloc(엠얼록이라고 읽는다) 함수부터 알아보자. 인수로 필요한 메모리양을 바이트 단위로 전달하면 요청한만큼 할당한다. size_t는 메모리의 양을 나타내는 단위인데 _t로 끝나는 사용자 정의 타입은 표준에 의해 반드시 정의하도록 되어 있으므로 기본 타입과 거의 대등한 자격을 가진다. 플랫폼에 따라 다르게 정의되어 있는데 대부분의 32비트 컴파일러들은 size_t를 unsigned의 부호없는 정수형으로 정의한다. 따라서 이 함수로 할당할 수 있는 이론적 최대 용량은 4G 바이트라고 할 수 있다.

10바이트가 필요하면 malloc(10)이라고 호출하고 1000바이트가 필요하면 malloc(1000)이라고 호출하면 된다. 실행중에 할당하는 것이므로 malloc(Num)과 같이 변수도 사용할 수 있다. malloc은 응용 프로그램이 필요로하는 양만큼 운영체제에게 할당을 요청하며 운영체제는 사용되지 않는 빈 영역(힙)을 찾아 요청한만큼 메모리를 할당하여 그 시작 번지를 리턴한다. 응용 프로그램이 할당한 메모리를 어떤 목적에 사용할지는 알 수 없으므로 malloc은 void *형을 리턴하며 받는 쪽에서는 원하는 타입으로 캐스팅해야 한다.

free 함수는 동적으로 할당한 메모리를 해제한다. 응용 프로그램은 메모리를 다 사용한 후에 반드시 free 함수를 호출하여 메모리를 해제해야 한다. 그래야 이 영역이 다른 프로그램을 위해 재활용될 수 있다. 다음 코드는 정수형 변수 10개를 담을 수 있는 메모리를 할당하는 예이다.

 

int *ar;

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

// ar 사용

free(ar);

 

동적으로 할당된 메모리를 사용하려면 그 시작 번지를 기억해야 하므로 포인터 변수가 필요하다. 이 경우 정수형 변수를 위한 메모리를 할당하는 것이므로 정수형 포인터 ar을 선언했다. malloc을 호출할 때는 필요한 메모리양을 바이트 단위로 전달하는데 정수형 변수 10개를 담을 수 있는 메모리의 총 크기는 10*sizeof(int), 즉 40바이트여야 한다. int가 항상 4바이트라는 보장이 없으므로 여기에도 반드시 sizeof 연산자로 크기를 계산해야 한다.

malloc은 인수로 전달된 크기만큼의 메모리를 할당하고 그 시작 번지를 리턴하되 리턴 타입이 void *형이므로 이 포인터를 변수에 대입할 때는 반드시 원하는 타입으로 캐스팅할 필요가 있다. 물론 void *형 변수로 받을 수도 있는데 이렇게 하면 받을 때는 편하지만 쓸 때마다 캐스팅해야 하므로 더 불편하다. 정수형 포인터에 대입해야 하므로 (int *)로 캐스팅했다. 이렇게 할당하면 ar 번지 이후 40바이트를 응용 프로그램이 배타적으로 소유하게 된다.

즉 포인터 ar이 가리키는 번지는 마치 ar[10] 정수형 배열과 같아지며 메모리내에서의 실제 모양과 용도, 적용되는 문법도 배열과 동일하다. ar 번지 이후의 40바이트는 다른 목적에 사용되지 않으므로 마치 정적 할당된 배열처럼 이 메모리를 활용할 수 있다. 물론 다 사용하고 난 후에는 반드시 free 함수로 메모리를 해제해야 한다. 그럼 동적 할당의 실제 예를 보도록 하자.

 

: malloc

#include <Turboc.h>

 

void main()

{

     int *arScore;

     int i,stNum;

     int sum;

 

     printf("학생수를 입력하세요 : ");

     scanf("%d",&stNum);

     arScore=(int *)malloc(stNum*sizeof(int));

     if (arScore == NULL) {

          printf("메모리가 부족합니다.\n");

          exit(0);

     }

 

     for (i=0;i<stNum;i++) {

          printf("%d번 학생의 성적을 입력하세요 : ",i+1);

          scanf("%d",&arScore[i]);

     }

 

     sum=0;

     for (i=0;i<stNum;i++) {

          sum+=arScore[i];

     }

 

     printf("\n총점은 %d점이고 평균은 %d점입니다.\n",

          sum,sum/stNum);

     free(arScore);

}

 

배열을 공부할 때 만들어 본 적이 있는 성적 처리 프로그램이다. 차이점이라면 학생 수가 미리 고정되어 있지 않고 실행 직후에 입력받은 학생수만큼의 성적을 처리할 수 있다는 점이다. 1명이든 100명이든 또는 10만명이든 메모리 한계까지 성적을 처리할 수 있다. 실행 결과는 다음과 같다.

 

학생수를 입력하세요 : 3

1번 학생의 성적을 입력하세요 : 55

2번 학생의 성적을 입력하세요 : 77

3번 학생의 성적을 입력하세요 : 88

 

총점은 220점이고 평균은 73점입니다.

 

실행 직후에 총 학생 수를 stNum에 입력받고 stNum만큼의 정수를 저장할 수 있도록 arScore 배열을 할당했다. 만약 malloc이 NULL을 리턴하면 성적 처리를 계속할 수 없으므로 메모리가 부족하다는 에러 메시지를 출력하고 프로그램을 종료해야 한다. stNum 크기의 배열을 동적으로 할당했으므로 arScore는 stNum 크기의 정수형 배열과 똑같아지며 이후 코드에서는 arScore를 정적 할당된 배열과 똑같은 방법으로 사용할 수 있다.

arScore[i] 연산식으로 i번째 요소를 자유롭게 읽고 쓸 수 있으며 해제하기 전까지 이 메모리는 다른 응용 프로그램이 건드릴 수 없는 독점적인 공간이 되는 것이다. 모든 작업이 끝나면 free 함수로 할당된 메모리를 해제해야 한다. 동적 할당이란 사실 굉장히 간단한 것이다. 필요한만큼 malloc으로 할당해서 쓰다가 다 쓰고 나면 free로 해제하기만 하면 된다.

다음은 malloc과 free 함수에 대한 참고 사항이다. malloc 함수는 할당에 실패하면 에러의 표시로 NULL을 리턴하며 그래서 이 함수를 호출할 때는 위 예제처럼 malloc이 리턴한 번지를 반드시 점검하는 것이 원칙이다. 메모리가 부족한 상황은 언제든지 발생할 수 있고 만약 이 점검을 하지 않으면 0번지를 액세스할 위험이 있다. 제대로 만든 프로그램은 어떠한 극한 상황에서도 최소한 죽지는 말아야 한다.

그러나 32비트 환경은 메모리가 충분할 뿐만 아니라 운영체제의 메모리 관리 기법이 정교해져서 작은 메모리를 할당할 때는 에러 점검을 생략해도 큰 무리가 없다. 얼마 정도가 작은지에 대한 명확한 기준은 없지만 일반적으로 메가 단위 이상을 할당할 때는 꼭 점검해야 하며 수십~수백 바이트 정도는 굳이 점검하지 않아도 상관없다. 메모리 부족 사태는 이제 응용 프로그램만의 책임이 아니다. 만약 malloc(100) 호출이 실패하는 상황이 발생한다면 응용 프로그램이 다운되기 전에 운영체제가 먼저 이 상황을 처리하도록 되어 있다. 에러 점검 코드를 일일이 작성한다면 안전성이나 이식성면에서 더 좋기는 하겠지만 그만큼 실행 속도가 느려지고 크기도 늘어나는 반대 급부가 있고 운영체제의 무리한 메모리 확장 시도로 인해 다운되는 것보다 더 치명적인 상태가 될 위험도 있다.

다음은 할당된 메모리를 해제하지 않았을 때의 문제점에 대해 알아보자. malloc으로 할당만 하고 free를 하지 않으면 메모리 관리 원칙상 이 메모리는 시스템을 재부팅하기 전에는 다른 응용 프로그램이 사용하지 못한다. 그래서 16비트 환경에서 해제를 하지 않으면 시스템 메모리가 감소되며 할당된 채로 남아 있는 영역에 의해 메모리가 조각난다. 이렇게 되면 다음 프로그램이 실행될 충분히 큰 공간이 없어 시스템 다운으로 이어지기도 한다.

그러나 32비트 운영체제는 메모리를 할당한 프로그램이 종료되면 해제하지 않은 메모리를 알아서 회수하도록 되어 있으므로 16비트에서처럼 큰 문제가 되지는 않는다. 그렇다고 해서 할당만 해 놓고 해제하지 않는 것이 좋다거나 그래도 괜찮다는 얘기는 절대로 아니다. 다만 16비트 환경보다는 문제가 덜하다는 것뿐이지 할당 후 해제하는 것은 프로그래밍의 대원칙이다. malloc후에 항상 free하는 것을 잊지 말아야 하며 malloc 코드를 칠 때 아래 쪽에 free를 먼저 입력해 놓고 다음 작업을 하는 습관을 들이는 것이 좋다.