C Dynamic Memory Allcation
malloc은 연속된 메모리 블록을 예약하기 위해 사용되는 C library call입니다
Stack memory와는 달리, 같은 포인터를 free 해주기 전까지 그 메모리는 계속 남아있게 됩니다.
아래에서 이야기할 calloc와 realloc 또한 마찬가지입니다.
Can malloc fail?
정확한 프로그램을 만들려면 malloc의 return값을 체크해야 합니다.
만약 항상 malloc이 성공한다고 가정하고 코드를 짰다면, malloc에 실패한 후 그 곳에 작성하려고 할 때 0번지에 쓰게 되므로 segfalut가 발생하게 될 것입니다.
Where is the heap and how big is it?
프로세스 메모리가 어떻게 구성되어 있는지 한번 살펴봅시다.
프로세스란 프로그램에서 현재 동작중인 instance이다. 각각의 프로세스는 각자의 주소 공간을 가지고 있습니다. 예를 들어 32비트 컴퓨터에서 프로세스는 약 40억개의 주소를 사용할 수 있습니다. 하지만 이 모든 주소가 유효한 것도 아니고 Physical memory와 매핑되어 있는 것도 아닙니다. 이 프로세스 메모리 안에는 executable code, stack space, environment variable, global(static) variable, heap이 포함되어 있습니다.
만약 프로그램이 더 많은 heap memory를 필요로 한다면 sbrk 함수를 호출해 heap의 크기를 증가시킬 수 있습니다. heap과 stack(각각의 Thread마다 하나)은 고정된 크기가 아니고 증가할 필요가 있으므로 주소공간 상 서로 반대편에 놓이게 됩니다. 일반적인 아키텍쳐에서는 heap은 위쪽 방향으로, stack은 아래 방향으로 증가합니다.
트루시니스 : Modern OS의 memory allocator들은 더이상 sbrk가 필요하지 않습니다. 대신 가상 메모리의 독립적인 영역을 요청해 여러개의 memory region을 유지합니다. 예를 들어 기가바이트를 요청하면 조그만 allocation으로 요청했던 것과는 다른 memory 영역을 요청할 것입니다. 하지만 이런 자세한 내용은 오히려 내용을 더 복잡하게만 만들 수 있기 때문에 여기에서는 heap이 single region인 것으로 가정하여 설명하겠습니다.
만약 우리가 multi-thread program을 구현한다면 각각의 thread마다 하나의 stack이 필요하지만 heap은 하나만 존재합니다.
일반적인 아키텍쳐에서 heap은 data segment의 일부분이고, code와 global variable 바로 위에서 시작합니다.
Do programs need to call brk or sbrk?
특별히 현재 heap의 마지막 주소가 어딘지 알고 싶을 때를 제외(sbrk(0))하면 일반적으로는 사용할 필요가 없습니다.
대신 프로그램은 C library에 있는 malloc, calloc, realloc과 free를 사용합니다. 이러한 함수의 내부에서 추가적인 heap memory가 필요할 때 sbrk가 호출될 것입니다.
void *top_of_heap = sbrk(0); malloc(16384); void *top_of_heap2 = sbrk(0); printf("The top of heap went from %p to %p \n", top_of_heap, top_of_heap2);
위의 예제처럼 코드를 작성하게 되면 sbrk(0)가 현재 heap의 마지막 주소값을 리턴하므로 heap의 끝을 알 수 있습니다.
What is calloc?
void *calloc(size_t n, size_t size) { size_t total = n * size; // Does not check for overflow! void *result = malloc(total); if (!result) return NULL; // If we're using new memory pages // just allocated from the system by calling sbrk // then they will be zero so zero-ing out is unnecessary, memset(result, 0, total); return result; }
// Ensure our memory is initialized to zero link_t *link = malloc(256); memset(link, 0, 256); // Assumes malloc returned a valid address! link_t *link = calloc(1, 256); // safer: calloc(1, sizeof(link_t));
위처럼 malloc후 memset을 해주는 것과 calloc을 호출해 주는 것은 같은 동작입니다.
Why is the memory that is first returned by sbrk initialized to zero
만약 OS가 physical ram의 내용물을 0으로 초기화하지 않고 보내준다면 하나의 프로세스가 다른 프로세스가 사용했던 메모리를 알 수 있을 것입니다. 이것은 security leak으로 이어질수 있습니다.
하지만 이것은 어떤 프로그램 내에서 이전에 malloc으로 사용했다가 free로 풀어준 메모리가 0으로 초기화된다는 뜻은 아닙니다.이전에 malloc으로 작업했다가 free시킨 후 다시 malloc으로 할당받으면 0이 아닌 값이 되어있을 수도 있습니다. 안전한 코드를 짜려면 malloc후 0으로 초기화시켜주는 것이 필요할 것입니다.
char* ptr = malloc(300); // contents is probably zero because we get brand new memory // so beginner programs appear to work! // strcpy(ptr, "Some data"); // work with the data free(ptr); // later char *ptr2 = malloc(308); // Contents might now contain existing data and is probably not zero
위의 코드에서 보듯이 malloc을 잡은 후 그 포인터를 사용한 후에 free 시켜준 이후에 다시 malloc으로 호출하는 것은 0으로 초기화되어 있지 않을 수도 있습니다.
Why doesn't malloc always initialize memory to zero?
효율성을 위해 malloc은 항상 0으로 초기화하지 않습니다. 0으로 초기화하는 작업이 필요하다면 calloc이나 malloc 후 memset을 통해 0으로 초기화해주어야 합니다.
What is realloc and when would you use it?
realloc은 malloc, callol, realloc 등으로 기존에 heap에 할당되어 있던 메모리의 크기를 조절해 다시 할당해 줍니다. realloc의 가장 일반적인 사용법은 array의 값을 유지한 채 크기를 증가시켜 주는 것입니다. realloc의 동작을 읽기 쉽게 작성한 코드는 아래와 같습니다.
void * realloc(void * ptr, size_t newsize) { // Simple implementation always reserves more memory // and has no error checking void *result = malloc(newsize); size_t oldsize = ... //(depends on allocator's internal data structure) if (ptr) memcpy(result, ptr, newsize < oldsize ? newsize : oldsize); free(ptr); return result; }
위 코드에서 보면 입력받은 크기만큼 새로운 메모리 공간을 heap에 할당하고 그 곳에 기존에 있던 메모리의 값을 memcpy를 통해 복사해 주고 원래 포인터는 free시켜준 뒤 새로운 포인터를 할당해줍니다.
다음의 코드는 realloc을 잘못 사용한 예입니다.
int *array = malloc(sizeof(int) * 2); array[0] = 10; array[1] = 20; // Ooops need a bigger array - so use realloc.. realloc (array, 3); // ERRORS! array[2] = 30;
위 코드에는 두가지 실수가 있습니다.
첫번째로는 realloc을 3바이트만 한 것입니다. int는 보통 32bit 컴퓨터에서 4바이트를 차지하는데, realloc을 통해 3바이트만 할당했으므로 오히려 증가해야하는 크기가 더 작아지게 됩니다.
둘째로는 realloc은 현재 존재하는 포인터의 메모리를 덧붙여서 증가시키거나 감소시키거나 하는 것이 아니라 완전히 새로운 곳으로 복사를 하는 것이므로 리턴값을 받아 사용해야 합니다. 이렇게 하는 이유는 기존에 할당된 메모리 옆에 다른 데이터가 존재할 수 있으므로 기존 공간이 아닌 다른 공간에 새로 할당한 후 기존 공간을 free 시켜주기 때문입니다. 기존에 인자로 넘겨준 값은 free되므로 return 값을 받아 사용해야 합니다.
array = realloc(array, 3 * sizeof(int)); // If array is copied to a new location then old allocation will be freed.
즉, 위와 같이 사용해야 합니다.
더 정확한 코드를 작성하기 위해서는 return값이 NULL인지도 체크해주어야 합니다.
realloc은 할당된 메모리의 크기를 증가시키거나 감소시킬 때 사용한다는 것을 기억합시다!
How important is that memory allocation is fast?
memory allocation의 속도는 매우 중요합니다. 대부분의 application에서 heap memory의 allocation과 deallocation은 매우 흔한 operation이기 때문입니다.
'Angrave System Programming > Memory and Allocators' 카테고리의 다른 글
Smashing the Stack Example (0) | 2019.01.14 |
---|---|
Implementing a Memory Allocator (0) | 2019.01.07 |
Heap Memory Introduction(2) (0) | 2019.01.04 |