Memory API

박정빈·2024년 3월 19일

운영체제

목록 보기
9/25

UNIX 시스템에서의 메모리 할당 인터페이스에 대해 알아보자

Types of Memory 메모리의 종류

C 프로그램을 실행하는 동안 할당되는 메모리에는 두 가지 유형이 있다.
첫 번째는 스택 메모리이다. 이에 대한 할당 및 해제는 컴파일러가 암시적으로 관리한다. C에서 스택에 메모리를 선언하는 것은 쉽다.

void func(){ int x; ...}

컴파일러가 이 함수 func()를 호출할 때 스택에 공간을 확보한다. 함수가 return하면 컴파일러가 메모리를 해제한다.
두 번째는 힙 메모리이다. 모든 할당 및 해제는 프로그래머가 처리한다.

void func(){ int *x=(int *) malloc(sizeof(int)); ... }

스택 및 힙 할당이 위의 한 줄에서 발생한다. 컴파일러는 포인터를 정수로 선언할 때 해당 포인터에 대한 공간을 확보한다.(int *x); 이후 프로그램이 malloc()을 호출 할 때 정수에 대한 공간을 힙에 요청한다.

malloc()

malloc() 호출은, 힙에 공간을 요청하고 성공시 그 공간에 대한 포인터를 반환하고 실패시 NULL을 반환한다.

#include <stdlib.h>
...
void *malloc(size_t size);

malloc()의 단일 매개변수는 크기를 나타내는 size_t 유형이다. 예를 들어 sizeof() 연산자를 사용하여 double에 대한 공간 양을 요청할 수 있다.

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

sizeof()에 변수 이름을 전달할 수도 있다.

int *x = malloc(10 * sizeof(int));
printf("%d\n", sizeof(x));

하지만 원하는 결과를 얻지 못할 수도 있다. 위와 같은 상황에서 x에 10개의 정수 배열 공간을 선언했다. 하지만 sizeof(x)는 정수에 대한 포인터 크기를 반환한다.

아래의 경우는 컴파일러에 40Byte가 할당되었다는 정적 정보가 충분하다.

int x[10];
printf("%d\n", sizeof(x));

또, 주의할 점은 문자열이다. 문자열에 공간을 할당할 때 다음 관용구를 사용한다. malloc(strlen(s) + 1) strlen() 함수를 사용하여 문자열 길이+1 을 추가한다. 여기서 sizeof()를 사용하면 문제가 발생한다.

free()

더 이상 사용하지 않는 힙 메모리를 해제하려면, free()를 호출한다. malloc()에 의해 반환된 포인터를 하나의 인수로 취한다.

int *x = malloc(10 * sizeof(int));
...
free(x);

Common Errors 자주 발생하는 오류들

malloc(),free() 사용 시 자주 발생하는 오류들이 있다. 이 오류들은 컴파일러에서 경고를 주지 않고 컴파일 될 수 있기에 위험하다.

메모리 할당을 잊는 경우

예를 들어, strcpy()는 문자열을 소스 포인터에서 대상 포인터로 복사한다.

char *src = "hello";
char *dst; // 이런! 할당되지 않음
strcpy(dst, src); // 세그멘테이션 폴트 발생하여 프로그램이 종료됨

위와 같이 메모리를 할당하지 않고 사용하면 segmentation fault가 발생할 수 있다.

char *src = "hello";
char *dst = (char *) malloc(strlen(src) + 1);
strcpy(dst, src); // 제대로 작동함

메모리를 충분히 할당하지 않는 경우

이 경우는 버퍼 오버플로우라고도하는 버퍼에 충분한 공간을 할당하지 않는 것이다.

char *src = "hello";
char *dst = (char *) malloc(strlen(src)); // 너무 작음!
strcpy(dst, src); // 올바르게 작동함

이 경우는 문자열을 복사할 때 문자열 마지막의 null값을 복사할 공간을 주지 않은 경우이다. 실행은 되지만 보안 취약점의 원인이 될 수 있다.

할당된 메모리 초기화를 잊는 경우

새로 할당된 영역에 값을 채우지 않는다면 힙의 메모리를 할당했기에 원래 그곳에 있던 값을 읽을 수 있다. 이 값은 무슨 값인지 모르기에 위험하다.

메모리 해제를 잊는 경우

이 경우는 메모리 누수로 알려져 있다. 메모리 누수가 계속 되면, 결국 메모리가 부족해지고, 다시시작해야할 수 있다. 가비지 컬렉터를 사용한다고 해서 안심할 수 없다. 메모리에 대한 참조가 있으면 가비지컬렉터는 그것을 해제하지 않으며, 메모리 누수가 생길 수 있다.

프로그램의 수명이 짧고 곧 종료될 것이라면 free()를 사용하지 않는 것이 합리적이라고 생각할 수 있지만, 이것은 나쁜 습관이며 모든 메모리를 해제하는 것이 좋다.

사용이 끝나기 전에 메모리를 해제하는 경우

프로그램이 메모리를 사용하는 동안 메모리를 해제하면, 프로그램을 충돌 시킬 수 있거나 중요한 메모리 값을 덮어쓸 수 있다.

메모리를 반복해서 해제하는 경우

이런 이중 해제를 할 경우 충돌이 발생한다.

free()를 잘못 쓰는 경우

free()의 매개변수로 해제하고 싶은 포인터를 받게 되는데 잘못된 포인터를 해제하는 경우 문제가 발생할 수 있다.

Underlying OS Support

malloc()과 free()를 논의할 때 시스템 호출(system call)에 대해 언급하지 않았는데, 그것들이 라이브러리 호출(library call)이기 때문이다. 따라서 malloc 라이브러리가 system call을 사용해서 메모리를 관리한다.
그 중 하나가 brk라는 system call 이다. 이것은 프로그램의 브레이크 위치, 즉 힙의 끝 위치를 변경하는데 사용된다. 새로운 브레이크의 위치를 인수로 취하여, 새로운 브레이크가 현제 브레이크보다 큰지 작은지에 따라 힙의 크기를 증가 또는 감소시킨다. 추가적으로 sbrk같은 호출이 있지만, 이런 system call을 직접 호출하면 안된다. malloc(),free()를 사용해야한다.

Other Calls 다른 호출들

  • mmap()
    호출을 통해 운영 체제에서 메모리를 얻을 수도 있다. 올바른 인수를 전달하면 mmap()은 프로그램 내에 익명 메모리 영역을 생성할 수 있다. 이 메모리 영역은 스왑 공간(swap space)이나 힙처럼 쓸 수 있다.
  • calloc()
    메모리를 할당하고 0으로 초기화한다. 따라서 메모리를 초기화하지 않는 실수를 예방할 수 있다.
  • realloc()
    새로운 메모리 영역을 만들고 이전 영역을 복사한 다음 새 영역의 포인터를 반환한다.

0개의 댓글