[System Programming] 동적 메모리 할당 (Dynamic Memory Allocate)
프로그래머에게는 메모리에 대한 이해가 중요합니다.
메모리에 대한 이해가 중요한 이유는 다음과 같습니다.
- 메모리는 무한한 자원이 아닙니다.
- 메모리 참조 버그는 매우 치명적입니다. 버그의 영향이 시공간적으로 동떨어져서 발견되고, Run-time 에 버그가 발생하는 경우 실제 세계에서 큰 손실을 초래할 수 있습니다.
- 메모리의 성능은 일정하지 않습니다. CPU 와 가까운 메모리공간인 Cache는 속도가 빠르지만, Memory 나 Auxillary Memory는 Cache보다 속도가 느립니다. 따라서 메모리 시스템의 특성을 잘 반영하는 프로그램을 디자인해야 만족스러운 성능을 얻을 수 있습니다.
그냥 코드를 짜면, 컴파일러가 알아서 메모리를 고려하여 컴파일 해주는데 왜 동적 메모리를 사용하고 관리하는 법을 알아야할까요?
그 이유는 프로그램이 실행되기 전에는 크기를 알 수 없는 자료 구조를 사용하기 위해서입니다. 예를 들어 게임서버를 설계하는데 최대 동시접속자 수를 고려하는 중이라고 합시다. 최대 동시접속자 수를 무조건 Maximum Size 로 잡고 설계하면, 최대 동시접속자 수보다 적은 인원의 유저들이 이용하는 동안은 서버의 자원이 낭비되게 됩니다. 반면, 최대 동시접속자 수를 작게 잡으면, 그보다 많은 인원이 접속하고자 할 때 에러가 날 것입니다. 따라서 동적으로 메모리를 사용/관리하는 법을 알아야합니다.
동적 메모리 할당기 (Dynamic Memory Allocator) 는 두가지 종류로 구분할 수 있습니다.
- 직접 메모리 할당기 (Explicit) : 응용 프로그램이 할당하고, 반환합니다. (e.g., malloc 과 free)
- 간접 메모리 할당기 (Implicit) : 응용 프로그램이 할당하지만, 반환은 하지 않습니다. (e.g., JAVA 의 Garbage Collector).
두가지 동적 메모리 할당기 모두 메모리를 블럭단위를 제공합니다. (Computer Architecture)
응용 프로그램에 가용 메모리 블럭(free 상태의 메모리 블럭)을 할당합니다.
여기서는 직접 메모리 할당에 대해서 다루도록 하겠습니다.
프로세스의 메모리 이미지를 살펴보도록 하겠습니다.
- Program Text 는 Instruction 을 저장하는 영역입니다.
- Initialzied Data 는 initialize 된 Global 변수를 저장하는 영역입니다.
- uninitialized Data 는 initialized 되지 않은 Global 변수를 저장하는 영역입니다.
- brk포인터는 esp(스택 포인터)와는 대조적으로 위로 성장합니다.
- esp(스택 포인터)는 아래로 성장합니다.
다음으로, malloc, free, realloc (Malloc Package)에 대해 알아보도록 하겠습니다.
#include <stdlib.h> : malloc 은 stdlib 에 내장되어있습니다.
void *malloc(size_t size)
성공적으로 메모리를 확보한 경우, malloc은 적어도 size 바이트의 메모리 블록의 포인터를 반환합니다. (cache를 최대한 많이 사용할 수 있도록 메모리를 확보합니다. 예를 들어 내가 10bytes를 요청해도, 더 큰 크기의 메모리 블럭이 할당될 수 있습니다. 대게 8바이트(64bits) 단위로 맞추어 반환합니다.) 만일 size 가 0이라면 NULL을 반환합니다.
성공적으로 메모리를 확보하지 못한 경우, malloc은 NULL을 반환하고, errno을 세팅합니다.
void free(void *p)
블록 포인터 p가 가르키는 블록을 가용 메모리 공간으로 전환합니다. p는 이전에 malloc 이나 realloc 에서 제공된 블록포인터입니다.
void *realloc(void *p, size_t size)
블록 p의 크기를 변경하고, 새 블록의 포인터를 리턴합니다. 새 블록의 내용은 이전 블록과 새 블록의 크기 중 작은 크기까지는 변화가 없습니다.
malloc 을 사용하는 예시를 살펴보겠습니다.
먼저 malloc 을 통해 (int) 사이즈의 n개의 메모리 블럭을 확보합니다. 그리고 realloc 을 통해 블록의 크기를 (n+m) 으로 확장합니다. realloc 이후의 for 문에서 p메모리 블럭의 n번 주소부터 initialzing 하지만 p[0] ~ p[n-1] 까지는 malloc 이후 for문에서 할당한 값들과 동일합니다.