[C]동적메모리 할당 함수(malloc, calloc, realloc, free)


이번 포스트는 동적메모리 할당 함수에 관한 내용입니다.


1️⃣ 동적 메모리 사용

  1. 메모리 할당
    • 힙관리자에게 메모리를 “몇 바이트” 달라고 요청 (어떤식으로 관리해주는지는 환경에 따라 다릅니다)
    • 관리자는 연속되는 “몇 바이트”만큼의 메모리를 찾아서 반환 (메모리 주소로 반환되기 때문에 포인터 변수에 저장)
  2. 메모리 사용
    • 할당된 메모리이상을 사용하면 힙 오버플로우(heap overflow)가 발생합니다.
  3. 메모리 반납/해제
    • 힙관리자에게 그 메모리 주소를 돌려주면서 다 썼다고 알려줌 (관리자는 그 메모리 주소를 아무도 사용하지 않는 상태로 바꿉니다)

2️⃣ malloc함수

< 함수원형 >

void malloc(size_t size);
  • 메모리 할당(memory allocation)의 약자 입니다.
  • 힙메모리를 할당해줍니다.
  • void*로 반환하기 때문에 (char*)malloc(40)과 같이 형변환을 통해 사용하면 됩니다.
  • 할당과 동시에 초기화를 안해줍니다. 그렇기 때문에 반환된 메모리에 들어있는 값은 쓰레기 값입니다.
  • 메모리가 더 이상 없다거나 해서 실패하면 NULL을 반환합니다.
  • 사용 후에는 반드시 free함수를 통해 메모리를 해제해야 합니다.

3️⃣ free함수

< 함수원형 >

void free(void* ptr);
  • malloc함수를 통해 할당받은 메모리를 해제시켜주는 함수입니다.
  • 즉, 메모리 할당 함수들을 통해서 얻은 메모리만 해제가 가능합니다. 만약 ptr이 할당된 메모리가 아니라면 어떤일이 일어날지 모릅니다(심각한 버그가 일어날 수도..).
    반면, ptrNULL포인터라면 free함수는 아무것도 하지 않습니다.
      free(ptr);
      ptr = NULL;
    

    위의 코드와 같이 해제시켜준 ptr주소에 NULL포인터를 지정해주면 실수로 ptrfree함수로 여러번 해제시켜주더라도 안전합니다.

  • 적절히 메모리를 해제시켜주지 않을시 메모리 누수(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함수는 메모리 시작 주소가 변하지 않는 경우 데이터를 복사하지 않아 성능상 효율적일 수 있습니다.
  • 상황에 따라서 적절한 방법으로 사용하면 될 것 같습니다.




© 2021.02. by kirim

Powered by kkrim