malloc
, calloc
, realloc
같은 동적 메모리 할당 함수들은 C 언어의 표준 라이브러리 함수들이다.
실제로는 운영체제(OS)에서 제공하는 시스템 호출(syscall)을 통해 메모리를 할당하는 과정으로 이루어진다.
1. malloc
의 내부 동작
malloc
은 사용자가 요청한 크기의 메모리를 힙 영역에서 할당하는 함수이다.
malloc
이 어떤 흐름으로 메모리를 할당하는지 알아보자
기본 흐름
- 사용자가 메모리를 요청:
malloc
에 의해 사용자는 지정한 크기의 메모리를 할당받기를 원한다. - 힙 영역에서의 메모리 관리: C 프로그램은 스택(Stack)과 힙(Heap)이라는 두 가지 주요 메모리 영역을 사용한다. 힙은 프로그램 실행 중에 동적으로 메모리를 할당하는 데 사용된다. 힙은 연속된 메모리 블록으로 관리되며, 메모리 블록은
free list
또는block list
라는 자료 구조로 관리된다. 이 목록에는 이미 할당된 블록과 아직 할당되지 않은 여유 공간이 포함된다. - 메모리 블록 검색:
malloc
은free list
또는block list
에서 사용 가능한 충분한 크기의 메모리 블록을 검색한다. 이때 일반적으로 최초 적합(first fit), 최적 적합(best fit), 최악 적합(worst fit)과 같은 알고리즘 중 하나를 사용하여 블록을 선택한다. - 시스템 호출(Sbrk): 만약 적절한 크기의 메모리 블록을 찾을 수 없으면,
malloc
은 시스템 호출인sbrk()
또는mmap()
을 사용하여 커널에 새로운 메모리 페이지를 요청한다. 이 시스템 호출은 운영체제로부터 새로운 메모리 영역을 받아 힙 영역을 확장한다.sbrk(increment)
함수는 힙을 확장하여 새로운 메모리 블록을 추가하는 데 사용된다.mmap
은 메모리 매핑을 통해 메모리를 할당하며, 특히 큰 메모리 할당 시 많이 사용된다.- 페이지에 대한 자세한 내용은 https://developsvai5096.tistory.com/91 이글을 자세히 정리 되있다.
- 메모리 블록 분할: 요청한 크기의 메모리 블록이 기존 블록보다 작은 경우,
malloc
은 메모리 블록을 분할하여 필요한 만큼만 할당하고, 나머지를 다시free list
에 추가하여 재사용할 수 있도록 한다. - 메모리 할당: 최종적으로 적절한 크기의 메모리 블록이 확보되면, 해당 메모리 블록의 시작 주소를 반환한다. 이때 반환된 포인터는 해당 메모리 영역의 첫 번째 바이트를 가리키게 된다.
실제 흐름을 간단한 의사 코드로 표현:
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
의 내부 동작
calloc
은 malloc
과 거의 비슷한 방식으로 동작하지만, 추가로 할당된 메모리를 0으로 초기화하는 과정이 있다.
기본 흐름
- 메모리 크기 계산:
calloc
은 두 개의 인자를 받는다. 첫 번째는 요소의 개수(num
), 두 번째는 각 요소의 크기(size
)이다.calloc
은 내부적으로 두 값을 곱해 총 메모리 크기를 계산한다. malloc
호출:calloc
은 내부적으로malloc
을 호출하여 계산된 크기만큼의 메모리를 할당받는다.- 초기화:
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
은 이미 할당된 메모리 블록의 크기를 재조정하는 함수로, 새로운 크기의 메모리 블록을 요청하고, 기존 데이터는 그대로 유지한다.
기본 흐름
- 메모리 크기 비교:
realloc
은 새로운 크기의 메모리를 할당해야 할지 결정한다. 기존 할당된 메모리 블록의 크기와 요청된 크기를 비교한다. - 기존 메모리 재사용: 기존 블록이 새로 요청된 크기보다 클 경우, 추가적인 할당이 필요하지 않으며 기존 블록을 그대로 반환한다.
- 메모리 재할당: 만약 기존 블록이 충분하지 않다면, 새로운 메모리 블록을
malloc
을 통해 할당한다. - 데이터 복사: 새로운 블록이 할당되면, 기존 메모리 블록의 데이터를 새로운 메모리로 복사한다. 이때
memcpy
를 사용하여 데이터를 복사한다. - 기존 블록 해제: 새로운 메모리 블록이 성공적으로 할당되고 데이터가 복사된 후, 기존 블록은
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 |