본문 바로가기

Studying/Computer Programs

C언어 동적 메모리 할당 (malloc, free 함수)

일반적으로 C언어에서 변수나 배열을 선언하면, 해당 코드블록이 끝나서 범위를 벗어날때 까지 메모리를 차지하게 됩니다. 하지만 이렇게 되면 메모리 공간의 낭비가 발생할 수 있죠. 필요할 때만 메모리에 변수를 저장하기 위한 공간을 할당해서 사용하는 것이 해결책이 될 수 있고, 이를 동적 메모리 할당 (dynamic memory allocation)이라고도 부릅니다.

 

반응형

 

동적 메모리 할당을 위해서는 먼저 포인터라고 하는 변수가 필요한데요. 메모리 상에서의 주소를 저장하기 위한 변수인 포인터의 개념이 생소하게 느껴지는 분들에게는 다음 포스팅이 큰 도움이 되리라 생각합니다.

 

 

C언어 프로그래밍 기초 : 포인터

여기서는 C언어와 C++가 다른 프로그래밍 언어와 차별화되는 요소 중 하나인 포인터 (pointer)에 대해서 알아봅시다. 포인터의 기본 개념과 함께 일반적인 데이터를 저장하는 변수들과의 상관관계

swstar.tistory.com

 

malloc, free 함수

동적 메모리 할당을 위해서 mallocfree 함수를 사용할 수 있고, 이들을 호출하려면 stdlib.h 헤더 파일을 포함시켜야 합니다. 이 함수들은 포인터 변수를 다루고 있으며, 할당된 공간의 맨 첫부분의 메모리 주소가 됩니다. 예를 들어서 정수형 변수 10개를 위한 공간을 할당받아서 사용하고 싶다면 다음과 같은 방식을 사용할 수 있습니다.

 

int *ptr_array;

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

/* ptr_array 에 할당된 변수 사용
 * 예시 : ptr_array[i] = i */
for (int i = 0; i < 10; i++) {
    ptr_array[i] = i;
}

free(ptr_array);

 

메모리 할당을 위한 malloc 함수는 할당받고자 하는 메모리 공간의 크기를 인자로 받고 있는데요. 여기서는 10개의 정수형 변수가 들어갈 만큼의 공간을 필요로 하기 때문에, 정수형 변수의 크기를 알기 위해 sizeof 함수를 호출하고 10을 곱했습니다. 그렇게 해서 메모리 할당이 제대로 되면, malloc 함수는 할당된 공간의 메모리 주소를 리턴합니다. 위에 나온 예시에서는 그 주소가 정수형 포인터 변수인 ptr_array에 저장된 것을 볼 수 있죠. 만약 물리적 메모리가 부족하다거나 하는 등의 이유로 메모리 할당이 제대로 되지 않았다면, malloc 함수는 가리키는 대상이 없는 널 (NULL)포인터를 리턴합니다.

 

이제 malloc 함수로부터 리턴 받은 메모리 주소를 통해, 할당된 공간의 변수들의 값을 바꾸거나 이용할 수 있습니다. 접근하고자 하는 변수의 번호 또는 인덱스를 대괄호 ([ ], square bracket)안에다가 넣고 포인터 변수 이름 뒤에다가 붙이면 되는데요. 이는 사실 배열에 저장된 변수에 접근하는 방식과 동일한 것입니다. 위의 예시에서처럼 포인터 변수의 이름이 ptr_array라면, i-번째 변수의 값은 ptr_array[i] 가 됩니다. N개의 변수가 들어갈 수 있는 공간을 할당받았다면 i0 부터 N-1 까지의 값을 가질 수 있습니다. 만약 이 범위를 벗어나서 할당받지도 않은 공간에 접근하려 들면, segmentation fault 또는 런타임 오류가 뜨게 되겠죠.

 

한 가지 더 짚고 넘어갈 점이라면, malloc 함수는 void 형 포인터를 리턴한다는 점인데요. 그렇기 때문에 저장하고자 하는 변수의 자료형에 걸맞게 형변환을 해서 문제의 소지를 줄일 필요가 있습니다. 만약 정수형 변수라면 (int *), 문자 변수라면 (char *) 등을 malloc 함수 앞에 붙여서, 리턴이 되자마자 형변환을 해 주면 되겠습니다.

 

마지막으로, 할당받은 메모리 공간을 더 사용할 필요가 없어졌다면 free 함수를 호출하여 이들을 풀어줘야 합니다. 이를 제때 해제하지 않고, 이런 것들이 쌓이다 보면 소위 말하는 메모리 누수 (memory leak)가 되겠죠.

 

어떤 함수가 행렬이나 텐서 등을 변수로 받아야 할 때가 있는데, 다중배열만 가지고는 이걸 구현하기가 쉽지 않기 때문에 동적 메모리 할당이 해결책이 될 수 있습니다. 위에 나온 방법을 쓰면, 행의 갯수가 nrow 이고 열의 갯수가 ncolumn 인 실수 행렬을 받는 함수를

 

void function(double **ptr,
    int nrow, int ncolumn) {
    
    // ... 함수의 내용 ...
}

 

같은 형식으로 쉽게 구현이 가능하니까 말이죠.

 

realloc 함수

이름에서 유추할 수 있듯이 realloc 함수는 이미 할당받았던 메모리에 저장된 변수들의 값들을 유지하면서, 다른 크기의 공간을 재할당 받는 데 사용됩니다. 이 함수의 프로토타입을 살펴보면 다음과 같습니다.

 

/* ptr : 기존에 할당받았던 메모리 공간의 주소
 * s : 할당받고자 하는 메모리 공간의 크기
 * 새로 할당된 공간의 메모리 주소를 리턴 */
void* realloc(void *ptr, size_t s);

 

예전에 malloc 함수 등을 통해서 할당받은 메모리 공간의 주소와 새롭게 할당받고자 하는 공간의 크기를 매개변수로 받고 있습니다. malloc 함수와 비슷하게 새로 할당된 공간의 메모리 주소를 리턴합니다. 참고로 NULL 포인터를 인자로 주게 되면, realloc 함수는 malloc 함수와 동일한 방식으로 작동하는데요. 이말인즉슨 메모리에 새롭게 공간을 할당하고 그 주소를 리턴한다는 뜻입니다.

 

예시를 통해 realloc 함수를 좀 더 자세히 살펴봅시다.

 

더보기
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[]) {
    int size_now;
    int *ptr_array_old;
    int *ptr_array_new;

    size_now = 10;
    ptr_array_old = NULL;
    ptr_array_new =
        (int *)realloc(ptr_array_old,
                       size_now * sizeof(int));

    fprintf(stdout,
            "  1 : size = %d\n", size_now);
    fprintf(stdout,
            "      ptr_array_new =");
    for (int i = 0; i < size_now; i++) {
        ptr_array_new[i] = i + 1;
        fprintf(stdout,
            "  %d", ptr_array_new[i]);
    }
    fprintf(stdout, "\n");

    size_now = 15;
    ptr_array_old = ptr_array_new;
    ptr_array_new =
        (int *)realloc(ptr_array_old,
                       size_now * sizeof(int));

    for (int i = 10; i < size_now; i++) {
        ptr_array_new[i] = i + 1;
    }

    fprintf(stdout,
            "  2 : size = %d\n", size_now);
    fprintf(stdout,
            "      ptr_array_new =");
    for (int i = 0; i < size_now; i++) {
        fprintf(stdout,
            "  %d", ptr_array_new[i]);
    }
    fprintf(stdout, "\n");

    size_now = 5;
    ptr_array_old = ptr_array_new;
    ptr_array_new =
        (int *)realloc(ptr_array_old,
                       size_now * sizeof(int));

    fprintf(stdout,
            "  3 : size = %d\n", size_now);
    fprintf(stdout,
            "      ptr_array_new =");
    for (int i = 0; i < size_now; i++) {
        fprintf(stdout,
            "  %d", ptr_array_new[i]);
    }
    fprintf(stdout, "\n");

    free(ptr_array_new);

    return 0;
}

 

첫번째 단계에서는 정수형 변수가 10개 들어갈 수 있는 크기의 공간을 할당받았는데, 이 때는 realloc 함수의 인자로 NULL 포인터가 들어갔으므로 새로운 공간을 할당받게 됩니다. 그리고 10개의 정수형 변수의 값을 정하고 출력하게 되죠.

 

두번째 단계에서는 할당받은 공간의 크기를 15로 증가시켰는데요. 기존에 할당받았던 공간에 자리잡고 있던 10개 변수의 값들은 그대로 유지되었습니다. 반면에 새로 확보된 공간에 있는 5개의 변수들에는 새로 값을 정의해 줘야 합니다. 그렇지 않으면 소위 쓰레기 값들이 들어가게 되고, 이는 잘못하면 버그로 이어질 수 있습니다.

 

세번째 단계에서는 할당받은 공간의 크기를 5로 줄였습니다. 그 결과 맨 처음 5개 변수들의 값은 유지된 반면에, 나머지는 다 사라지게 되었죠. 마지막으로는 free 함수를 호출하여 할당받은 메모리 공간을 해제합니다.

 

위의 예제 프로그램을 실행시켜 보면 다음과 같은 결과를 얻을 수 있을 것입니다.

 

execution of a test program for realloc function

 


 

C++ 프로그램의 경우, newdelete 키워드들을 이용하면 메모리를 동적으로 관리하는 것이 가능합니다. 이 방법에 대한 자세한 사항은 다음 포스팅에 소개되어 있습니다.

 

 

C++ 동적 메모리 할당 (new, delete 키워드)

여기서는 new 및 delete 키워드를 통해 C++ 프로그램에서 메모리 공간을 동적으로 할당받고 해제하는 방법에 대해서 짚어보겠습니다. 보통 변수나 객체를 소스 코드 내에서 선언하게 되면, 선언한

swstar.tistory.com

 

여기서는 변수의 포인터를 언급했지만, 포인터의 포인터를 가지고 동적인 메모리 할당을 하면 포인터의 배열을 만드는 것도 가능합니다. 이는 함수의 포인터에 대해서도 마찬가지이므로, 수치해석에서 유용하게 활용할 수 있죠. 대표적인 예시는 다음 포스팅에 소개되어 있습니다.

 

 

C/C++ Runge-Kutta 방법으로 알아보는 진자운동

이번 포스팅에서는 천장에 매달린 진자의 운동에 대한 썰을 수치해석과 함께 한번 풀어볼까 합니다. 중력이 복원력으로 작용하는 왕복운동은 고등학교 물리 교과서에도 등장할 정도로 친숙하

swstar.tistory.com

 


 

같이 알고 있으면 좋은 C/C++ 팁들

 

명령행 인자

 

Command-Line Arguments (C/C++ 공통)

C++를 배우기 위해 책을 보는데, command-line arguments에 대한 내용이 있었다. 메인함수를 특별한 방법으로 정의해서, 프로그램을 실행시킬때 커맨드 라인에서 옵션을 지정해줄 수 있다고 한다. int mai

swstar.tistory.com

 

함수의 포인터를 인자로 사용하기

 

C/C++ 에서 함수를 매개변수로 사용하기

일반적으로 C언어나 C++ 에서 사용하는 함수의 경우, 인자(매개변수) 혹은 파라미터로 변수를 받아갑니다. 이 값들을 가지고 정의된 기능을 수행하게 되죠. 하지만 프로그램을 만들다 보면 함수

swstar.tistory.com

 

포트란과의 하이브리드

 

C/C++와 Fortran을 조합한 프로그래밍

수치계산을 위한 프로그램을 짜다 보면, 포트란 함수를 C 혹은 C++ 에서 쓰거나, 그 반대의 경우가 생길 때가 있습니다. 특히 역사가 좀 오래된 모델을 가져다 쓸 때 그렇죠. C언어에서 함수를 특

swstar.tistory.com

 

라이브러리 만들기

 

C언어 라이브러리 만들기

자주 쓰는 함수들을 라이브러리 형태로 만들어 두면, 프로그램을 짤 때 편리합니다. 예를 들어서 함수 Function이 mylib.c 라는 소스코드에 정의되어 있고, 프로토타입이 mylib.h 라는 헤더 파일에 선

swstar.tistory.com