동적 할당 함수

왜 쓰는가?

프로그램에 필요한 저장 공간은 프로그램을 작성할 때, 변수나 배열 선언을 통해 확보된다. 하지만 때로는 프로그램 실행 중에 저장 공간을 할당해야 할 수도 있다. 이때 사용한 저장 공간은 실행 중에 재활용을 위해 반납해야 한다. 이렇게 프로그램 실행 중에 저장 공간을 할당하는 것을 동적 할당(dynamic allocation) 이라고 한다.

malloc 함수, free 함수

프로그램 실행 중에 메모리를 동적 할당할 때는 malloc함수를, 반환할 때는 free함수를 사용한다.
이 함수들을 사용할 때는 stdlib.h 헤더 파일을 include 해야한다.

malloc함수

void *malloc(unsigned int size);
//malloc함수의 원형

malloc 함수는 주어진 인수의 바이트 크기만큼 메모리에서 연속된 저장 공간을 할당한 후에 그 시작 주소를 반환한다. 힙 영역에 할당할 수 있는 적당한 블록이 없을 때에는 널 포인터를 반환한다.
주소값을 반환받기 때문에 힙 영역에 할당된 메모리 공간으로 접근하려면 포인터를 사용해야 한다.

malloc 함수는 (void *)형을 반환 하기 때문에, 실제 함수에서는 용도에 맞는 포인터 형으로 형변환을 해서 사용한다.

int* pi;
pi = (int* )malloc(sizeof(int));

여기에서 (int*)으로 malloc함수의 반환값인 void포인터를 형 변환 해서 사용하는 것을 알 수 있다.
그리고 malloc함수의 인수로 직접 바이트값을 계산하여 인수로 줄 수도 있지만, sizeof 연산자로 각 자료형의 인수 크기를 계산해 주는 것이 좋다. 그러면 컴파일러에 따라 데이터 타입의 크기가 다르더라도 프로그램을 수정할 필요가 없어진다.

주의사항

  1. malloc함수의 반환값이 NULL 포인터인지 반드시 확인하고 사용해야 한다.
    malloc을 비롯한 메모리 할당 함수는 원하는 크기의 공간을 할당하지 못하면 0번지 NULL포인터를 반환한다. NULL포인터는 특별한 상태를 나타내기 위해 사용하므로 간접 참조 연산을 할 수 없다.
    그러므로 동적 할당 함수를 호출한 이후, 이 함수가 혹시 NULL 포인터를 반환하고 있지는 않은지 반드시 검사하는 과정이 필요하다.
if(pi == NULL){
	printf("메모리가 부족합니다.\n");
    exit(1); 
    //exit 함수는 프로그램 어디에서든 프로그램을 바로 종료하는 함수다. 
    //예외 상황이 발생하여 프로그램을 바로 종료하는 경우 인수를 1로 주고 호출할 수 있다.
}
  1. 사용이 끝난 저장 공간은 재활용 할 수 있도록 반환해야 한다.
    동적으로 할당한 저장 공간은 함수가 반환된 후에도 그대로 메모리에 남아있다. 따라서 반환되기 전에 동적 할당한 공간은 free함수를 이용해 반환해야 한다.
    메모리 해제를 잊으면 메모리 누수를 일으켜 프로그램이 의도치 않게 종료될 수 있다.

free 함수

free 함수는 힙 영역에 할당받은 메모리 공간을 다시 운영체제로 반환해주는 함수이다.

void free(void *p);
//free함수의 원형

인수의 타입이 void형 포인터로 설정되어 있으므로 어떤 타입의 포인터라도 전달될 수 있다.
실제로는 저장공간의 사용이 끝났을 때 다음과 같이 반환할 수 있다.

free(pi);

동적 할당 영역을 배열처럼 쓰기

형태가 같은 변수가 많이 필요할 때 하나씩 동적 할당하는 것은 비효율적이다. 할당하는 저장 공간의 수만큼 포인터가 필요하기 때문이다. 따라서 크기가 충분히 큰 저장 공간을 한꺼번에 동적 할당해 배열처럼 사용하는 편이 좋다. 이때 할당한 저장 공간의 시작 위치만 포인터에 저장하면 포인터를 배열처럼 쓸 수 있다.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int* pi;
    int i, sum = 0;
    
    pi = (int*)malloc(5 * sizeof(int)); // 5*4=20바이트 만큼의 저장 공간 할당
    
    if (pi == NULL)
    {
    	printf("메모리가 부족합니다.\n");
        exit(1);
    }
    
    printf("다섯 명의 나이를 입력하세요.");
    
    for (i = 0;i < 5;i++)
    {
    	scanf("%d", &pi[i]); //할당한 메모리를 배열처럼 사용
        sum += pi[i];
    }
    
    printf("다섯 명의 평균 나이: %.1lf\n", (sum / 5.0));
    free(pi);
    
    return 0;
    
}

이와 같은 과정을 이용해 할당된 메모리는 배열처럼 배열 요소 표현식을 이용하여 값을 입력할 수 있다. 물론 pi + i처럼 포인터 계산식을 직접 사용하는 것도 가능하다.

기타 동적 할당 함수 calloc, realloc

calloc

calloc 함수는 malloc과 유사하지만 할당한 저장 공간을 0으로 초기화 한다.

double* pd;
pd = (double*)calloc(5, sizeof(double));//배열 요소의 개수, 바이트 크기

realloc

저장공간의 크기를 조정해야 할 때 쓴다. realloc 함수는 이미 할당한 저장공간의 포인터와 조정할 저장 공간의 전체 크기(추가 또는 감소시킬 크기가 아님)를 준다.

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

저장 공간을 늘리는 경우 늘어난 부분에는 쓰레기 값이 존재하며, 줄이는 경우에는 입력된 데이터가 잘려나간다.
이미 사용하던 저장공간의 주소를 알고 있다고 하더라도, 재할당 과정에서 메모리의 위치가 바뀔 수 있기 때문에 realloc 함수가 반환하는 주소를 다시 포인터에 저장해 사용하는 편이 좋다.
왜냐하면 기존의 메모리 위치에 충분한 공간이 없다면 메모리의 다른 공간에 기존의 데이터를 복사한 후, 이어서 추가 메모리 공간을 할당하기 때문이다.

프로그램이 사용하는 메모리 영역과 그 특징

메모리 영역은 프로그램이 올라가는 코드 영역과 데이터가 저장되는 데이터 영역으로 나뉜다.
데이터 영역은 지역 변수가 할당되는 스택 영역이 있고, 동적 할당되는 공간으로 힙 영역이 있다. 그 외에 전역변수나 정적 변수를 위한 기타 데이터 영역이 있다.

힙에 할당한 저장 공간 역시 지역 변수와 마찬가지로 쓰레기값이 존재한다. 그러나 지역 변수와 달리 프로그램이 종료될 때까지 메모리에 존재한다. 이러한 특징 때문에 주소만 알면 특정 함수에 구애받지 않고 어디에서나 사용할 수 있다는 장점이 있지만, 반드시 공간 사용이 끝나면 메모리 공간을 반환해야 한다.

메모리에 저장 공간이 남아있어도 메모리 할당 함수들이 NULL 포인터를 반환할 수 있따. 힙 영역은 메모리의 사용과 반환이 불규칙적이기 때문에 사용 가능한 영역이 조각나서 흩어져 있을 수도 있다. 이때 연속된 큰 저장공간을 요구하면 동적 할당 함수는 원하는 저장 공간을 찾지 못하고 널 포인터를 반환할 수 있다.

그렇기 때문에 동적 할당 함수를 호출한 후에는 반드시 반환값을 검사해서 메모리의 할당 여부를 확인해야 한다.

이 글은 서적 '혼자 공부하는 C언어'의 16장과 TCP SCHOOL을 참고하여 작성하였습니다.

profile
Fear always springs from ignorance.

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN