Clang

[크래프톤 정글] malloc, calloc, realloc

하루이2222 2024. 10. 7. 14:34

malloc, calloc, realloc 같은 동적 메모리 할당 함수들은 C 언어의 표준 라이브러리 함수들이다.

실제로는 운영체제(OS)에서 제공하는 시스템 호출(syscall)을 통해 메모리를 할당하는 과정으로 이루어진다.

1. malloc의 내부 동작

malloc은 사용자가 요청한 크기의 메모리를 힙 영역에서 할당하는 함수이다.

malloc이 어떤 흐름으로 메모리를 할당하는지 알아보자

기본 흐름

  1. 사용자가 메모리를 요청: malloc에 의해 사용자는 지정한 크기의 메모리를 할당받기를 원한다.
  2. 힙 영역에서의 메모리 관리: C 프로그램은 스택(Stack)과 힙(Heap)이라는 두 가지 주요 메모리 영역을 사용한다. 힙은 프로그램 실행 중에 동적으로 메모리를 할당하는 데 사용된다. 힙은 연속된 메모리 블록으로 관리되며, 메모리 블록은 free list 또는 block list라는 자료 구조로 관리된다. 이 목록에는 이미 할당된 블록과 아직 할당되지 않은 여유 공간이 포함된다.
  3. 메모리 블록 검색: mallocfree list 또는 block list에서 사용 가능한 충분한 크기의 메모리 블록을 검색한다. 이때 일반적으로 최초 적합(first fit), 최적 적합(best fit), 최악 적합(worst fit)과 같은 알고리즘 중 하나를 사용하여 블록을 선택한다.
  4. 시스템 호출(Sbrk): 만약 적절한 크기의 메모리 블록을 찾을 수 없으면, malloc은 시스템 호출인 sbrk() 또는 mmap()을 사용하여 커널에 새로운 메모리 페이지를 요청한다. 이 시스템 호출은 운영체제로부터 새로운 메모리 영역을 받아 힙 영역을 확장한다.
    • sbrk(increment) 함수는 힙을 확장하여 새로운 메모리 블록을 추가하는 데 사용된다.
    • mmap은 메모리 매핑을 통해 메모리를 할당하며, 특히 큰 메모리 할당 시 많이 사용된다.
    • 페이지에 대한 자세한 내용은 https://developsvai5096.tistory.com/91 이글을 자세히 정리 되있다.
  5. 메모리 블록 분할: 요청한 크기의 메모리 블록이 기존 블록보다 작은 경우, malloc은 메모리 블록을 분할하여 필요한 만큼만 할당하고, 나머지를 다시 free list에 추가하여 재사용할 수 있도록 한다.
  6. 메모리 할당: 최종적으로 적절한 크기의 메모리 블록이 확보되면, 해당 메모리 블록의 시작 주소를 반환한다. 이때 반환된 포인터는 해당 메모리 영역의 첫 번째 바이트를 가리키게 된다.

실제 흐름을 간단한 의사 코드로 표현:

void* malloc(size_t size) {
    if (size == 0) return NULL;

    // 블록 목록에서 적절한 크기의 블록을 검색
    Block* block = find_suitable_block(size);

    if (!block) {
        // 시스템 호출로 힙 확장 (sbrk or mmap)
        block = request_memory_from_os(size);
        if (!block) return NULL;
    }

    // 블록을 분할하여 남은 부분을 자유 목록에 다시 추가
    split_block_if_necessary(block, size);

    // 블록의 데이터 부분에 대한 포인터를 반환
    return block_to_data_ptr(block);
}

2. calloc의 내부 동작

callocmalloc과 거의 비슷한 방식으로 동작하지만, 추가로 할당된 메모리를 0으로 초기화하는 과정이 있다.

기본 흐름

  1. 메모리 크기 계산: calloc은 두 개의 인자를 받는다. 첫 번째는 요소의 개수(num), 두 번째는 각 요소의 크기(size)이다. calloc은 내부적으로 두 값을 곱해 총 메모리 크기를 계산한다.
  2. malloc 호출: calloc은 내부적으로 malloc을 호출하여 계산된 크기만큼의 메모리를 할당받는다.
  3. 초기화: calloc은 할당된 메모리 블록의 모든 바이트를 0으로 초기화한다. 이는 memset 또는 유사한 함수를 사용하여 이루어진다.

의사 코드로 표현:

void* calloc(size_t num, size_t size) {
    size_t total_size = num * size;
    void* ptr = malloc(total_size);  // malloc을 사용하여 메모리 할당
    if (ptr) {
        memset(ptr, 0, total_size);  // 메모리 초기화
    }
    return ptr;
}

3. realloc의 내부 동작

realloc은 이미 할당된 메모리 블록의 크기를 재조정하는 함수로, 새로운 크기의 메모리 블록을 요청하고, 기존 데이터는 그대로 유지한다.

기본 흐름

  1. 메모리 크기 비교: realloc은 새로운 크기의 메모리를 할당해야 할지 결정한다. 기존 할당된 메모리 블록의 크기와 요청된 크기를 비교한다.
  2. 기존 메모리 재사용: 기존 블록이 새로 요청된 크기보다 클 경우, 추가적인 할당이 필요하지 않으며 기존 블록을 그대로 반환한다.
  3. 메모리 재할당: 만약 기존 블록이 충분하지 않다면, 새로운 메모리 블록을 malloc을 통해 할당한다.
  4. 데이터 복사: 새로운 블록이 할당되면, 기존 메모리 블록의 데이터를 새로운 메모리로 복사한다. 이때 memcpy를 사용하여 데이터를 복사한다.
  5. 기존 블록 해제: 새로운 메모리 블록이 성공적으로 할당되고 데이터가 복사된 후, 기존 블록은 free를 통해 해제된다.

의사 코드로 표현:

void* realloc(void* ptr, size_t new_size) {
    if (!ptr) {
        // 기존 포인터가 NULL이면 새롭게 malloc 호출
        return malloc(new_size);
    }

    if (new_size == 0) {
        // 크기가 0이면 기존 메모리 해제하고 NULL 반환
        free(ptr);
        return NULL;
    }

    // 기존 블록의 크기 확인
    size_t old_size = get_block_size(ptr);

    if (new_size <= old_size) {
        // 새 크기가 기존 크기보다 작으면 기존 블록 그대로 사용
        return ptr;
    }

    // 새 크기의 메모리 블록 할당
    void* new_ptr = malloc(new_size);
    if (!new_ptr) return NULL;

    // 기존 데이터 복사
    memcpy(new_ptr, ptr, old_size);

    // 기존 블록 해제
    free(ptr);

    return new_ptr;
}

시스템 호출

동적 메모리 할당의 근본적인 단계는 운영체제에 메모리를 요청하는 시스템 호출이다. 여기서는 주로 두 가지 호출이 사용된다.

  • sbrk(increment): 힙 영역을 확장하는 가장 기본적인 시스템 호출로, 프로그램의 데이터 영역을 늘린다. 이 함수는 increment만큼 힙을 확장하고, 확장된 영역의 시작 주소를 반환한다. increment 값이 0이면 현재 힙의 끝 주소를 반환하여 힙의 위치를 알 수 있다.
  • mmap(): 메모리 맵핑을 통해 파일이나 장치와 메모리 공간을 연결하거나, 특정 크기의 메모리를 직접 할당하는 시스템 호출이다. 특히 큰 메모리 블록을 할당할 때 사용되며, 할당된 메모리는 커널에 의해 관리된다.

이 두 호출은 운영체제의 메모리 관리자와 커널에 의해 관리되며, 프로그램이 요청한 만큼의 메모리를 할당하고 힙 영역을 확장하는 데 사용된다.


메모리 관리

메모리 누수 방지

할당된 메모리는 사용이 끝나면 반드시 free로 해제해야 한다. 그렇지 않으면 메모리 누수가 발생해 프로그램이 필요 이상으로 많은 메모리를 차지하게 된다.

메모리 단편화

힙 영역에서 반복적인 할당과 해제가 일어나면 메모리가 쪼개지면서 단편화가 발생할 수 있다. 이로 인해 사용 가능한 메모리가 충분함에도 불구하고 연속된 큰 블록을 할당하지 못하는 상황이 발생할 수 있다. malloc 알고리즘이 이를 관리하기 위해 다양한 최적화 전략을 사용한다.


'Clang' 카테고리의 다른 글

[크래프톤 정글] 구조체 와 클래스 의 차이  (0) 2024.10.05
[크래프톤 정글 ] 포인터  (0) 2024.10.05