`realloc` — 메모리 영역을 재할당하는 함수

realloc — 메모리 영역을 재할당하는 함수

malloc으로 배열을 잡았는데, 나중에 보니 공간이 부족할 때가 있죠. 그럴 때 처음부터 다시 잡기보다, 기존 데이터를 유지하면서 크기만 바꾸는 게 편합니다. 그걸 하는 함수가 realloc이에요.

출처: cppreference — realloc

본문

realloc은 주어진 메모리 영역을 새 크기로 재할당해요.

문법

#include <stdlib.h>

void *realloc( void *ptr, size_t new_size );

설명

ptr이 NULL이 아니라면, 이전에 malloc, calloc 또는 realloc으로 할당됐고 아직 freerealloc으로 해제되지 않은 값이어야 해요. 그렇지 않으면 결과는 정의되지 않습니다.

재할당은 두 가지 방식 중 하나로 이루어져요.

  • (a) 가능하다면 ptr이 가리키는 기존 영역을 늘리거나 줄인다. 영역의 내용은 새 크기와 이전 크기 중 작은 쪽까지 그대로 유지되고, 늘어난 부분의 내용은 정의되지 않아요.
  • (b) new_size 바이트의 새 메모리 블록을 할당하고, 이전 크기와 새 크기 중 작은 쪽만큼의 메모리를 복사한 뒤 그 옛 블록을 해제한다.

메모리가 부족하면 옛 메모리 블록은 해제되지 않고 널 포인터가 돌아와요. ptr이 NULL이면 malloc(new_size)을 호출하는 것과 동일하게 동작합니다.

new_size가 0인 경우에는 표준 버전에 따라 달라집니다. C17까지는 동작이 구현에 정의됐어요(널 포인터가 돌아올 수도 있고 — 이때 옛 블록은 해제될 수도, 안 될 수도 있음 — 또는 저장소에 접근하면 안 되는 널 아닌 포인터가 돌아올 수도 있음). 이런 사용은 C DR 400을 통해 폐기(deprecated)됐고, C23부터는 동작이 정의되지 않습니다.

realloc은 스레드 안전(thread-safe)해요. 인자를 통해 보이는 메모리 위치만 접근하는 것처럼 동작하죠. 한 영역의 메모리를 해제하는 이전의 freerealloc 호출은, 같은 영역이나 그 일부를 할당하는 어떤 할당 함수 호출과도 동기화됩니다.

매개변수

매개변수 설명
ptr 재할당할 메모리 영역을 가리키는 포인터
new_size 배열의 새 크기(바이트)

반환값

성공하면 새로 할당된 메모리의 시작을 가리키는 포인터를 돌려줘요. 메모리 누수를 막으려면 이 포인터를 freerealloc로 해제해야 합니다. 원래 포인터 ptr은 무효화되고, 그 포인터로의 어떤 접근도 정의되지 않아요(재할당이 제자리에서 이뤄졌더라도).

실패하면 널 포인터를 돌려주고, 원래 포인터 ptr은 그대로 유효하므로 freerealloc로 해제해야 합니다.

주의

원래(C89에서) 0 크기 지원은 이런 코드를 수용하기 위해 추가됐어요.

OBJ *p = calloc(0, sizeof(OBJ));   // "zero-length" placeholder
/*...*/
while (1)
{
    p = realloc(p, c * sizeof(OBJ));  // reallocations until size settles
    /* code that may change c or break out of loop */
}

예시

malloc으로 초기 배열을 잡고 realloc을 반복하며 크기를 키우면서, 데이터가 유지되는지 확인하는 예시예요.

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

void print_storage_info(const int *next, const int *prev, int ints)
{
    if (next)
        printf("%s location: %p. Size: %d ints (%ld bytes).\n",
               (next != prev ? "New" : "Old"), (void*)next, ints, ints * sizeof(int));
    else
        printf("Allocation failed.\n");
}

int main(void)
{
    const int pattern[] = {1, 2, 3, 4, 5, 6, 7, 8};
    const int pattern_size = sizeof pattern / sizeof(int);

    int *next = NULL, *prev = NULL;

    if ((next = (int*)malloc(pattern_size * sizeof *next))) // allocates an array
    {
        memcpy(next, pattern, sizeof pattern);              // fills the array
        print_storage_info(next, prev, pattern_size);
    }
    else
        return EXIT_FAILURE;

    // Reallocate in cycle using the following values as a new storage size.
    const int realloc_size[] = {10, 12, 512, 32768, 65536, 32768};

    for (int i = 0; i != sizeof realloc_size / sizeof(int); ++i)
    {
        if ((next = (int*)realloc(prev = next, realloc_size[i] * sizeof(int))))
        {
            print_storage_info(next, prev, realloc_size[i]);
            assert(!memcmp(next, pattern, sizeof pattern));  // is pattern held
        }
        else                // if realloc failed, the original pointer needs to be freed
        {
            free(prev);
            return EXIT_FAILURE;
        }
    }

    free(next);             // finally, frees the storage
    return EXIT_SUCCESS;
}

가능한 출력:

New location: 0x144c010. Size: 8 ints (32 bytes).
Old location: 0x144c010. Size: 10 ints (40 bytes).
New location: 0x144c450. Size: 12 ints (48 bytes).
Old location: 0x144c450. Size: 512 ints (2048 bytes).
Old location: 0x144c450. Size: 32768 ints (131072 bytes).
New location: 0x7f490c5bd010. Size: 65536 ints (262144 bytes).
Old location: 0x7f490c5bd010. Size: 32768 ints (131072 bytes).

"New"는 재할당으로 주소가 새로 잡혔다는 뜻이고, "Old"는 제자리에서 크기만 바뀌었다는 뜻이에요. 크기를 늘려도 pattern 데이터가 그대로 보존되는 걸 assert로 확인합니다. 그리고 realloc이 실패하면 옛 포인터를 free로 해제하는 흐름도 눈여겨보세요.

더 알아보기

  • malloc — 메모리를 할당해요.
  • calloc — 0으로 초기화된 메모리를 할당해요.
  • cppreference의 realloc 원문에서 표준 항목별 세부 규칙을 더 확인할 수 있어요.