[C]동적메모리 할당 함수(malloc, calloc, realloc, free)
이번 포스트는 동적메모리 할당 함수에 관한 내용입니다.
1️⃣ 동적 메모리 사용
- 메모리 할당
- 힙관리자에게 메모리를 “몇 바이트” 달라고 요청 (어떤식으로 관리해주는지는 환경에 따라 다릅니다)
- 관리자는 연속되는 “몇 바이트”만큼의 메모리를 찾아서 반환 (메모리 주소로 반환되기 때문에 포인터 변수에 저장)
- 메모리 사용
- 할당된 메모리이상을 사용하면 힙 오버플로우(heap overflow)가 발생합니다.
- 메모리 반납/해제
- 힙관리자에게 그 메모리 주소를 돌려주면서 다 썼다고 알려줌 (관리자는 그 메모리 주소를 아무도 사용하지 않는 상태로 바꿉니다)
2️⃣ malloc함수
< 함수원형 >
void malloc(size_t size);
- 메모리 할당(memory allocation)의 약자 입니다.
- 힙메모리를 할당해줍니다.
void*
로 반환하기 때문에(char*)malloc(40)
과 같이 형변환을 통해 사용하면 됩니다.- 할당과 동시에 초기화를 안해줍니다. 그렇기 때문에 반환된 메모리에 들어있는 값은 쓰레기 값입니다.
- 메모리가 더 이상 없다거나 해서 실패하면 NULL을 반환합니다.
- 사용 후에는 반드시 free함수를 통해 메모리를 해제해야 합니다.
3️⃣ free함수
< 함수원형 >
void free(void* ptr);
- malloc함수를 통해 할당받은 메모리를 해제시켜주는 함수입니다.
- 즉, 메모리 할당 함수들을 통해서 얻은 메모리만 해제가 가능합니다. 만약
ptr
이 할당된 메모리가 아니라면 어떤일이 일어날지 모릅니다(심각한 버그가 일어날 수도..).
반면,ptr
이 NULL포인터라면 free함수는 아무것도 하지 않습니다.free(ptr); ptr = NULL;
위의 코드와 같이 해제시켜준
ptr
주소에 NULL포인터를 지정해주면 실수로ptr
을 free함수로 여러번 해제시켜주더라도 안전합니다. - 적절히 메모리를 해제시켜주지 않을시 메모리 누수(memory leak)이 발생합니다.
4️⃣ calloc함수
< 함수원형 >
void* calloc(size_t num, size_t size);
- 메모리를 할당할 때 자료형의 크기(size)와 갯수(num)을 따로 지정할 수 있습니다.
- 심지어 모든 바이트를 0으로 초기화 해줍니다.(malloc + memset을 사용한 효과)
- 하지만 잘 안씁니다.
5️⃣ realloc함수
< 함수원형 >
void* realloc(void* ptr, size_t new_size);
- 이미 존재하는 메모리(ptr)의 크기를 new_size값의 크기로 변경해줍니다.
- 새로운 크기가 허용하는 한 기존 데이터를 그대로 유지합니다(주소값도 그대로 유지될 수 있지만 아닐수도있습니다, 대신 성공시에 주소값이 바뀌게 된다면 기존메모리는 해제시켜줍니다.)
- malloc + memcpy + free의 기능을 다 해줍니다. (이 경우에는 다른 주소값을 반환)
realloc(NULL, LENGTH);
는malloc(LENGTH);
와 같은 기능을 합니다.- realloc함수는 기존주소값을 반환해주지만 만약 새롭게 지정해줄 크기가 커서 할당에 실패할시 NULL포인터를 반환합니다. 그렇기 때문에 잘못사용하면 메모리 누수가 발생할 수 있습니다.
< 메모리 누수가 발생할 수 있는 안좋은 예 >
void *num;
num = malloc(40);
num = realloc(num, 80);
- 만약
num
의 메모리 블럭이 빠듯하게 위치하고 있어서 추가로 메모리를 할당할 수 없게 된다면 위와같은 코드에서 realloc함수는 실패를 하게되고 NULL포인터를 반환하게 됩니다. (흔한 경우는 아닙니다) - 실패시 원래
num
에 저장되어있던 주소의 메모리가 해제되지않고 사라지게 됩니다. (찾을 방법이 없습니다) num
의 메모리를 해제시켜주지 못하고 메모리 누수(memory leak)가 발생합니다.
< 메모리 누수없이 올바르게 재할당하는 방법 >
void *num;
void *temp;
num = malloc(40);
temp = realloc(num, 80);
if (temp != NULL)
{
num = temp;
}
/* 코드 생략 */
free(num);
이런식으로 임시포인터temp
를 선언해서 사용하는 것은 기존 메모리의 포인터 주소를 지킬 수 있는 좋은 방법입니다. 이러한 방법은 포인터 연산을 사용하는 곳에서도 유용하게 사용됩니다.
< realloc함수대신 malloc+memcpy+free을 사용한 예 >
void *num;
void *temp;
num = malloc(40);
temp = malloc(num, 80);
if (temp != NULL)
{
memcpy(temp, num, 40);
free(num);
num = temp;
}
/* 코드 생략 */
free(num);
- malloc + memcpy + free조합으로 사용하는 것이 좀 더 명시적이고 좋을 수 도있습니다.(메모리누수면에서도 안전할 수 도..?)
- realloc함수는 메모리 시작 주소가 변하지 않는 경우 데이터를 복사하지 않아 성능상 효율적일 수 있습니다.
- 상황에 따라서 적절한 방법으로 사용하면 될 것 같습니다.