C에서 동적 메모리 할당

정글일지 19

출처 : https://aticleworld.com/dynamic-memory-allocation-in-c/

C에서 동적 메모리 할당

이 기사에서, 저장기간이 아닌 동적 메모리 할당과 실행기간동안 메모리를 할당하는데 사용되는 함수에 관해 얘기할 것이다. 동적 메모리 할당이 가진 각각의 장단점도 살펴볼 것이다.

C 표준에 따르면, 4개의 저장기간(storage duration)이 있다. : static, thread(C11), automatic, allocated. 저장기간은 객체의 일생(lifetime)을 결정한다. 일생은 객체가 살아있는 시간(저장이 보존된 시간)이며 이전에 저장된 값이 유지되는 시간이다. 객체의 일생 범위 밖에 접근하려는 행동은 정의되지 않았을 것이다.

C언어에서, 우리가 프로그램을 실행할 때 얼마나 메모리가 필요한지 모른다면 작동 시간에 변수가 된다. 예를 들어, 서버통신에서 우리는 반응 데이터(response data)의 정확한 크기를 모르기 때문에 두 가지 해결책을 준비한다. 하나는 충분한 크기의 buffer를 만드는것이고 또 하나는 실행할 때 메모리를 할당하는 것이다.

시스템에서 프로그램을 실행하면서 함수를 호출하고 함수를 위해 할당된 구조를 파괴함에 따라 스택은 크고 작아지기 때문에 컴파일동안 최악의 스택 크기를 예상하는 것도 어렵다. 스택 경계(stack boundary)를 넘을 수도 있기 때문에 거대한 buffer를 만드는 건 좋지 않다. 정의되지 않은 결과나 stack overflow를 초래할 수도 있다.

스택이 할당된 객체에 관한 또 다른 문제는 함수의 control에서 탈출한 후 파괴 되고, 우리가 다른 호출과 객체를 공유해야 하는 embedded system에서 흔하게 겪는 event-based architecture의 관점에서 문제를 야기한다는 것이다. 그래서 상술된 문제들을 해결하기 위해, C언어는 동적 메모리할당을 제공하는데 장점은 개발자가 파괴하기 전까지 그것들이 살아있다는 것이다.

우리가 컴퓨터 어플리케이션을 개발할 때 우리는 많은 메모리와 자원이 있다고 가정하지만 embedded 어플리케이션에서는 다르다. 작은 embedded 어플리케이션에서는 제한된 양의 자원과 메모리만 있기 때문에 신중헤야 한다.

C에서 메모리 관리 함수(memory management function)는 무엇일까?

C언어에서 malloc, calloc, realloc같은 많은 동적 메모리 할당을 위한 함수들이 있다. 동적 메모리 할당의 문제 중 하나는 사용자가 할당된 메모리를 해제(deallocate)하기 전까지 컴파일러가 직접 파괴할 수 없다는 것이다. 우리가 메모리 관리 함수를 사용할 떄, 그 함수들은 할당된 메모리 블록에 대한 포인터를 반환하고 메모리 블록의 시작 주소를 가리키는 포인터를 반환한다. 이용 가능한 공간이 없다면 함수는 null pointer를 반환한다.

C 표준에 따르면 요청된 공간의 크기가 0일 때, 그 요청은 implementation-defined이다. null pointer가 반환되거나, 반환된 포인터가 객체에 접근하는 데 사용되지 않아야 한다는 것을 제외하고는 크기가 0이 아닌 값인 것처럼 하거나

malloc

문법 : void *malloc(size_t size);

  • malloc 함수는 크기로 지정 객체를 위한 공간을 할당한다.
  • 할당된 공간의 값은 결정되지 않았다.
  • 이용가능한 공간이 없다면 NULL을 반환한다.

calloc

문법 : void *calloc(size_t nmemb, size_t object_size);

  • The calloc 함수는 nmemb 객체의 배열을 위한 공간을 할당하고 각각 객체의 크기와 같다.
  • 공간은 0으로 초기화된다.
  • calloc 함수는 null 포인터나 할당된 공간을 가리키는 포인터를 반환한다.

만약 할당된 메모리를 0으로 초기화하고 싶지 않다면, calloc보단 malloc을 쓰는게 낫다.

realloc

문법 : void *realloc(void *ptr, size_t size);

  • realloc 함수는 malloc과 calloc과는 달리 이전의 객체 할당을 없애고 새롭게 구체화된 크기를 다시 할당한다. 만약 새 크기가 이전 것보다 작다면 새롭게 할당된 메모리의 내용들은 이전과 똑같겠지만, 만약 새롭게 만들어진 객체의 바이트들이 이전것 이상이라면, 객체의 값은 결정되지 않는다.
  • 만약 piData가 null 포인터라면, realloc은 malloc 함수처럼 작동한다.
  • 만약 piData가 동적 할당 메모리가 아니라면, realloc의 작동은 정의되지 않는다.
  • 만약 piData가 할당이 취소된 메모리(메모리 블록 할당이 취소된 메모리)를 가리킨다면 realloc의 작동은 정의되지 않는다.
  • realloc 함수의 반환 값은 새로운 객체를 가리키는 포인터거나 새로운 객체가 할당되지 않는다면 null 포인터이다.

할당된 메모리를 자유화하기(free to deallocate the allocated memory)

문법 : void free(void *ptr);

  • free 함수는 동적 메모리 할당을 없앤다.
  • 만약 piData가 null 포인터라면, free 함수는 아무것도 하지 않는다.
  • 만약 piData가 위 함수들로 할당된 메모리를 가리키지 않는다면, free 함수의 작동은 정의되지 않는다.
  • 만약 piData가 free함수나 realloc함수를 이용해 비할당화된 메모리를 가리킨다면, free함수의 작동은 정의되지 않는다.

어떻게 C에서 동적 메모리를 할당하거나 비할당화할까?

C언어에서 동적 할당 메모리는 힙(heap)에서 온다. 만약 어떤 과정의 PCB(process control block)을 본다면, 힙과 스택의 방향은 반대일 것이다. 만약 너가 힙에서 거대한 양의 메모리를 할당할 것이라면 힙은 스택처럼 커지고 경계(boundary)를 넘을지도 모른다.

우리가 동적 메모리를 요구할때마다 우리는 요구된 타입의 포인터를 만들어야 하고 위의 함수들을 사용하여 메모리 블록을 할당해야 한다.

예시

char *piBuffer = malloc(5*sizeof(char));

우린 동적 메모리는 컴파일러가 파괴하지 않는다고 하였고, free 나 realloc 함수를 사용하여 할당된 메모리를 파괴하지 않는다면 메모리 누출(memory leak) 문제를 겪게 된다. 메모리 누출은 시스템 성능에 영향을 끼친다.

free(piBuffer); //Now pointer dangling pointer

piBuffer = NULL; //Now pointer is not more dangling

기억해야 할 포인트들

  • malloc, calloc같은 함수들로 할당된 메모리의 모든 블록은 free 나 realloc 함수로 비할당화 되어야 한다.
  • free 함수는 동적 메모리 할당에만 사용한다.
  • 단일 할당 메모리에 free 함수는 한번만 사용해야 한다.

C에서 동적 메모리 할당의 불이익

  • 상술했듯이, 컴파일러는 동적으로 할당된 메모리를 비할당화할 수 없고 개발자가 처리해야 한다. 만약 개발자가 잊어버렸다면, 메모리 누출을 야기하고 프로그램은 느려진다.
1
2
3
4
5
6
7
#include <stdlib.h>
int foo(void)
{
    int *piData = (int *) malloc(sizeof*(int));
    /* Do some work */
    return 0; /*Not freeing the allocated memory*/
}
  • 동적 메모리 할당은 메모리 단편화(fragmentation)의 원인이 될 수 있다.

예.

pcData1에 0.5k 할당

pcData2에 1k 할당

pcData3에 0.5k 할당

남은 2k가 자유롭다고 가정하자.(총 4k)

만약 pcData2를 비할당화한다면 총 3k가 자유로운 상태가 된다. 하지만 연속적이지 않기 때문에 3k 크기의 메모리를 할당할 수 없다.