본 내용은 OSTEP 의 내용을 정리 및 요약한 내용입니다. 전문은 이 곳을 방문하시면 보실 수 있습니다.

14. 막간 : 메모리 관리 API

UNIX 시스템에서의 메모리 관리 인터페이스 간략하게 소개한다.

핵심 질문 : 어떻게 메모리를 할당하고, 관리하는가?

14.1 메모리 공간의 종류

C 프로그래밍에서 메모리는 두 가지 영역으로 나뉘어 할당된다. 첫 째는 스택(stack), 둘째는 힙(heap) 영역이다.

스택(stack) 메모리는 할당과 반환이 컴파일러에 의해 암묵적으로 이루어 진다. 이러한 이유로 때론 자동(automatic) 메모리라고도 불린다.

그저 선언만 하면 되고, 선언 이후, 컴파일러는 나머지 작업을 수행하고, 함수가 종료되면 리턴되면서 프로그래머 대신 메모리를 반환한다.

힙 메모리는 오랫동안 값이 유지되어야 하는 변수를 위하며, 모든 할당과 반환이 프로그래머에 의해 명시적 으로 처리 되어야 하며, 정상적으로 관리되지 못하면 많은 버그의 원인이 되곤 한다.

힙 메모리의 이러한 특성 때문에 이 14과의 논점은 힙 메모리의 사용과 관리에 초점을 맞춘다.

14.2 malloc() 함수

#include <stdlib.h>

void * malloc(size_t size);

size 변수는 얼마나 메모리를 할당할지를 나타내며, 이때 정확한 크기를 제기 위해 sizeof() 를 활용하면 정확하게 그 사이즈를 나타낼 수 있다.

double * d = (double *) malloc(sizeof(double)); double * d = (double *) malloc(8);

위의 두 할당 예시는 동일하게 d라는 변수에 double 변수 1개 만큼의 공간을 동일하게 할당해준다. 단, sizeof 를 사용하면 실제 데이터 타입의 크기를 모르더라도 정확한 할당이 가능하다.

malloc() 은 void 타입에 대한 포인터를 반환한다. 이는 주소 자체만 넘겨주고 이 주소 내의 담긴 타입이 무엇이든 될 수 있고, 이를 바꾸는 것은 프로그래머가 결정하도록 유도하는 전형적인 C 방식의 타입변환(type casting)을 활용한 공간 결정 방식이다.

14.3 free() 함수

메모리의 할당 이후 이를 해제 하는 것은 사용 방법은 간단하지만, 한 편으로 언제 이를 해지할 지 정확하게 판단할 필요가 있다.

할당된 메모리의 크기는 할당 해제 할 때는 알 필요가 없고, 메모리 할당 라이브러리가 알고 있어야 하는 것이다.

...
int *x = malloc(10 * sizeof(int)); // int 배열 10칸짜리
... // 사용
free(x); // x 의 사용이 종료되어 해제한다.

14.4 흔한 오류

익히 아는 내용이지만… 복습 차원에서 정리하고자 한다.

  • 메모리 할당 잊어버리기
    char * src = "hello";
    char * dst;
    strcpy(dst, src); // segfault 발생 및 죽음...
  • 메모리를 부족하게 할당 받기 : 목적지 버퍼 공간을 약간 부족하게 할당해서, 제대로 들어가지 않는 경우다. 버퍼 오버플로우(buffer overflow)
    char * src = "hello";
    char * dst = (char * ) malloc(strlen(src)); // 작다!
    strcpy(dst, src); // 근데 동작은 하네? 버그 발생함(마지막 글자 짤림)
  • 할당 받은 메모리 초기화하지 않기 :
    초기화 하지 않고 사용을 하는 경우,프로그램은 결국 초기화되지 않은 읽기(unintialized read) 를 하게 된다. 여기서 핵심은 메모리 시스템 특성 상 메모리는 할당과 해제를 하는 과정에서 메모리의 공간을 따로 수정하는 것이 아닌, 관리 체계에서 해당 공간의 할당 여부를 확인하는 구조라는 것이다. 따라서 실제로 초기화하지 않았을 때, 메모리에 기록된 채 있던 쓰레기 값들을 그대로 사용하는 결과가 생길 수 있다.
  • 메모리 해제하지 않기 :
    메모리 누수(memory leak) 이라고 부르며, 프로그램 상에서 처음에는 문제가 없거나, 실제로 존재해도 종료만 잘 되면 문제 없는 경우도 있다. 하지만 장시간 실행되는 응용 프로그램에서는 이러한 메모리 누수가 천천히 사용자의 메모리 공간을 좀먹고, 시스템을 재시작하지 않으면 안되도록 만든다. 따라서 설령 프로그램 실행시간도 짧고, 운영체제에 의해 할당한 영역이 회수 될것 같아도 장기적으로 메모리 누수를 막기 위해 할당된 페이지를 정리하는 습관을 들이는 게 좋다.
  • 메모리 사용이 끝나기 전에 메모리 해제하기 :
    때때로 메모리 사용이 끝나기 전에 메모리를 해제 하는 경우가 있다. 이를 dangling pointer라고 부르며 심각한 에러를 야기할 수 있다. 만약 해제 후 다시 사용하게 되면, 없는 공간에 접근이 되고 프로그램의 크래시를 초래한다.
  • 반복적으로 메모리 해제하기 :
    이중 해제(double free)라고 하여 이미 해제 한 메모를 한 번더 요청하는 경우이며, 이 때 라이브러리는 어떻게 할 수가 없기에, 다양한 크래시가 발생할 여지가 있다.
  • free() 잘못 호출하기 :
    유효하지 않은 해제(invalid frees)라고 하며, 당연히 프로그램의 크래시를 초래하므로 피해야 한다.
  • 요약
    메모리의 오용은 여러 문제를 가진다. 이러한 문제를 발견하기 위해 다양한 툴들이 생겨났다. purify, valgrind가 바로 그것이다. 추가적으로 메모리 디버거들은 이 곳을 참고할 것을 추천한다.

14.5 운영체제의 지원

malloc()과 free()는 라이브러리 함수이고 시스템 콜 함수가 아니다. 사실 라이브러리가 프로세스의 가상 주소 공간 안에 공간을 관리하고 있고, 따라서 할당을 요구하는 것보다도 크게 받아 관리를 진행한다.

따라서 이러한 시스템 콜 함수를 사용하는 것은 결코 좋은 결과를 얻지 못한다.

추가로 mmap() 함수를 사용하여 운영체제에서 메모리 영역을 얻을 수도 있다. 올바른 인자를 전달하면 mmap() 함수는 프로그램에게 anonymous의 메모리 영역을 만들고, 이 영역은 특정 파일과 연결되어 있지 않고 swap space로 연결된 영역을 제공한다. 이 영역은 이후 가상 메모리 파트에서 논의할 예정이다.

14.6 기타 함수들

malloc()을 제외하고 메모리 할당을 위해서 다양한 함수들이 존재 한다. calloc()은 메모리 할당과 함께 그 영역을 0으로 초기화하는 역할을 하며 realloc()은 이미 할당된 공간을 더 크게 공간으로 만들어, 옛 영역의 내용을 복사한 후 새 영역에 대한 포인터를 반환한다.