[C] 동적 메모리 할당

https://hyeonistic.tistory.com/105

 

포인터와 구조체 설명하기..

팀 코어타임 이론 제공 전용 참고자료, C언어를 완전 처음 임하는 사람들에게 동기부여하는 내용이 주가 된다. 실질적인 사용 내용이랑은 거리가 좀 있지만, 나중 가서도 도움이 될 수 있는 점이

hyeonistic.tistory.com

이 글에 있는 내용 일부를 다시 끌어오겠다.

우리가 했던 프로젝트를 생각해보면, 백준의 알고리즘 문제도 그렇고 미니 프로젝트도 그렇고 그냥그냥 그랬다. 입력이 10만 들어온다고 하면 그냥 처음부터 10만 배열 만들어놓고 하고 그랬다. 실제 돈받고 팔아먹을 프로젝트에는 그렇게 할 수가 없다. 메모리 할당이 동적으로 일어나는 것이 실제 프로그램 성능에 영향을 미치기 때문, 아무리 이론적인 이야기만 하지만 그 밑에는 자본이 뒷받침 되어야한다. 즉, 동적 메모리 할당조차 유지비, 돈과 연결된 이야기라는 것. 이런 성능 최적화 연구 조차 알고리즘을 공부하는 것과 목적은 크게 다르지 않다는게 된다. 그래서 메모리 주소를 기억하고 다루는 것이 여전히 중요한 기술이 된 것이다.

왜 malloc과 free를 논 할 때 매개체로 주소값만 고려하나요? 다른 시도는 없었나요?
네. 현실적으로 말이 안됩니다. 주소값만큼 더 깊이 메모리를 가리킬 방법이 없음.

동적 메모리 할당에 대해 다뤄보겠다.

malloc(size_t size);

주어진 크기만큼 메모리를 할당하고, 할당된 메모리의 시작 주소를 반환한다. 이 메모리는 초기화되지 않는다.

 

calloc(size_t num, site_t size);

malloc과 유사하나 할당된 메모리를 0으로 초기화 한다.

  • 메모리를 0으로 초기화 한다는건 해당 주소에 있는 메모리 내용을 0으로 초기화 한다는 것.
  • float, int는 큰 의미가 없다. 뭐가 들어와도 주기된 주소의 비트들은 0으로 모두 초기화 된다.

 

realloc(void* ptr, size_t new_size);

이미 할당된 메모리 블록의 크기를 변경한다. 메모리 크기를 늘리거나 줄일 때 사용한다.

 

free(void* ptr);

할당 된 메모리를 해제한다.

왜 메모리 할당은 전부 byte 단위일까?

  • 그건 1원보다 더 낮은 1전을 다루는 행위 처럼 의미가 없어서 그렇다.
    • 뭐 어느 세상에는 1전을 소중하게 여기는 곳이 하나쯤은 있겠지만.. 보통은 1원도 그렇게 막 유의미하진 않지만, 1전보단 쓸 일이 있으니까. 같은 노력을 해서 1전 동전 꺼내는게 나을지, 1원을 꺼내는게 나을지 생각해보자.
  • 글자 하나를 표시하는 것도 1바이트를 써야한다. (char)
    • 그니까 1바이트 하나가 안만들어지는 형태 갖고 뭘 하겠냐?
      이런 생각을 하다보니 이 메모리 할당에 대한 최소 개념이 N byte가 되었다라고 생각하자.

malloc에 대해서 알아보자.

  • malloc은 메모리의 힙 영역에서 할당된다.
    • 이 메모리의 힙은 자료구조의 힙과 이름만 같지 매우 다른데, 동적 메모리 관리를 위한 저수지 같은 공간이다.
    • 정렬되어있는 느낌은 아닌데, 여기서의 힙은 정말 다양한 자료구조를 사용한다. 어떤 자료구조를 사용 할지는 운영체제가 재량껏 결정한다고 알려져있다.
    • 가상 주소 공간 중 일부인 힙은 고정된 크기가 아니며, 요청에 따라 늘어나거나 줄어든다.
      • 이에 대해 반드시 다루겠다..
    • 내부적 시스템 콜 : brk(), mmap() 중 하나를 한 용량에 대해 활용한다. 현대에는 mmap()을 더 많이 활용한다고 알려져 있다.
      •  brk()
        • 프로세스의 데이터 영역의 끝 주소를 설정하여 메모리를 할당하거나 해제한다.
        • brk()을 사용하면 힙의 끝을 이동 시킬 수 있다.
        • 진짜 직관적으로 예를 들어, 4바이트 int를 쓸 때, brk(+ 4바이트) | brk(- 4바이트) 이게 시스템 콜을 통해 이뤄진다.
        • 작은 크기에 있어서 제일 자주 활용된다. brk는 작은 메모리 블록 할당에 쓰인다.
      • mmap()
        • 페이지 단위인 4KB 단위로 메모리 할당을 가능하게 하며, 파일 또는 익명 메모리를 프로세스의 가상 메모리 영역에 매핑한다.
          • 익명 메모리는 파일을 쓰지 않는 메모리를 말한다. 파일이 없는 순수한 메모리 영역을 말한다.
            • 이게 좀 헷갈리는데, brk()도 결국엔 익명 메모리 영역을 할당한다. brk()는 대략 128KB 이내에서 배정된다. 128KB 이내의 파일도 있겠지만 현 시대엔 해당이 잘 없다.
        • 크게 분할된 메모리 요청을 할 때 효율적이다.
        • 파일을 메모리처럼 다룰 수 있는 기능때문에 파일 매핑하는데 더 적합하다.

메모리의 할당 전략

  • 각 선택지에 대해 논하는 것은 나중에 자세하게 논해보겠다.
    • First Fit : 처음 사이즈가 맞는 블록을 할당한다.
    • Best Fit : 딱 맞는 최소 크기의 블록을 찾아서 할당한다.
    • Worst Fit : 제일 큰 블록을 잘라서 쓴다.
    • 이 전략들에 따라 단편화에 영향을 준다.
      • 단편화에 대해 논해보자.
        • 외부 단편화는 공간이 여유로우나, 조각나 있어서 못쓰는 경우이다.
          • 무슨 일이 있어서 11MB를 배정하는데 12MB 블록을 할당해줬다. 이걸 1024번 반복했다.
          • 파편화된 1MB (= 즉, 사용되지 못한 용량의 블록)이 1024개 있으니 파편화된 용량이 1GB이다.
          • 근데 내가 여기에 1GB의 무언가를 할당하고 싶어도 연속되어있지 않으니 할당을 못한다.
        • 내부 단편화는 요청한 크기보다 크게 할당되어 낭비되는 공간이다.
          • 앞 케이스에 설명한 11MB 배정에 12MB 블록을 할당한 상황이다.
        • 현 시대에서 Memory Pool, Slab Allocator가 이런 단편화를 줄이기 위해 탄생했다.

흔한 실수

  • 메모리 누수 : Leak이 한글로 그대로 넘어오면서 누수로 번역이 되었다.
    • malloc 이후 사용이 다 끝났음에도 free 하지 않는 경우.
    • 프로그램 종료가 됨이 보장된다면 상관없지만 프로그램이 상주해야하는 경우 주요 할 일이 끝났다면 우선 free 하자.
  • 이중 해제 : *A가 C를 가리키고 *B도 C를 가리킨다.
    • free(A)를 했다. free(B)를 하면 Double Free 시도로 오류가 발생한다.
  • Dangling Pointer, Buffer Overflow 등 있지만 천천히 다른 글에서 다뤄보겠다.

C언어 계열 중에서 C#은 가비지 콜렉션을 쓴다. 물론 현대 고급 언어 대부분이 활용한다.

혼자 생각한 예제

게임에서 사운드 재생이 필요하다고 하자. 내 보조 기억장치에  sound.mp3 가 저장되어있다.

void* buffer = malloc(filesize);
fread(buffer, filesize, 1, fp);

이런식으로 시도하면 malloc()으로 확보한 공간에 내 sound.mp3를 가져 올 수 있다.
오디오 라이브러리에 buffer를 넘기면서 재생 할 내용을 넘기게된다. 라이브러리는 이제 적절히 잘 재생하면 된다.
재생이 끝났다면 free(buffer)로 마무리 한다.

'TIL' 카테고리의 다른 글

B-Tree (밸런스 트리)  (3) 2025.04.14
시스템 메모리에서의 스택과 큐  (0) 2025.04.13
포인터의 연산  (0) 2025.04.12
트리 순회에 대해 논하기  (0) 2025.03.27
TIL 0319  (0) 2025.03.19