이 포스팅은 Operating Systems: Three Easy Pieces, Remzi H. Arpaci-Dusseau & Andrea C. Arpaci-Dusseau을 읽고 개인 학습용으로 정리한 글입니다.
할당과 반환 컴파일러에 의해 암묵적으로 이루어짐
(= 자동(automatic) 메모리)
ex. func()라는 함수 안에서 x라고 불리는 정수를 위한 공간이 필요한 경우
void func(){
int x; //스택에 int 형을 선언
...
}
오랫동안 값이 유지되어야 하는 변수를 위함
모든 할당과 반환 프로그래머에 의해 명시적으로 처리
ex. func()라는 함수 안에서 x라고 불리는 정수에 대한 포인터를 힙에 할당하는 경우
void func(){
int *x = (int *) malloc(sizeof(int));
...
}
헤더 파일을 꼭 소스 코드에 포함시켜야 하는 것은 아님
-> 모든 C 프로그램에 디폴트로 링크되는 C 라이브러리는 malloc() 함수를 가짐
헤더 파일을 추가하면 malloc()을 올바르게 호출했는지 컴파일러가 검사 가능
-> 전달된 인자의 개수가 맞는지, 올바른 데이터 타임의 인자를 전달했는지 검사
정확한 크기의 공간을 요청하기 위해 sizeof()연산자 사용
ex. double precision의 부동 소수점 값을 위한 공간
double *d = (double *) malloc(sizeof(double));
int *x = malloc(10*sizeof(int));
printf("%d\n", sizeof(x));
-> 첫 번째 행에서 정수형 원소 10개를 가지는 배열을 위한 공간 선언
-> 두 번째 행에서 sizeof(x)를 사용하면, 동적으로 할당받은 메모리의 크기가 아닌 정수 포인터 x의 크기가 얼마인지 출력됨
int x[10];
printf("%d\n", sizeof(x));
-> 이 경우에는 변수 x에 40바이트가 할당되었다는 것을 컴파일러가 알 수 있는 정적인 정보가 충분하다
malloc(strlen(s)+1)
-> strlen() 함수를 사용하여 문자열의 길이 얻어냄
-> 문자열의 끝을 나타내는 문자(\n)을 위한 공간 확보를 위해 1바이트 더함
malloc()은 void 타입에 대한 포인터 반환
->해당 주소 공간에 어떤 타입의 자료를 저장할 지는 프로그래머가 결정하게 하는 C의 방식
프로그래머는 타입 반환(type casting) 을 이용하여 공간 활용 결정
-> 프로그램이 제대로 작동하는 데 있어서 캐스트가 반드시 필요한 것은 아님
int *x = malloc(10*sizeof(int));
...
free(x);
많은 루틴은 자신이 호출되기 전에 필요한 메모리가 미리 할당되었다고 가정한다
ex. strcpy(dst, src) 루틴: 소스 포인터에서 목적 포인터로 문자열 복사
char *src = "hello";
char *dst; //할당되지 않은 포인터
strcpy(dst, src); //segfault 발생
-> 이 코드를 실행하면 세그멘테이션 폴트(segmentation fault) 발생
char *src = "hello";
char *dst = (char *) malloc(strlen(src) + 1);
strcpy(dst, src); //제대로 동작
-> ⚡대안으로, strdup()을 사용할 수 있다
char *src = "hello";
char *dst = (char *) malloc(strlen(scr)); //너무 작다
strcpy(dst, src);
메모리 청크의 사용이 끝나면 반드시 해제해야 한다
메모리 누수 (memory leak)
= 메모리 해제를 잊었을 때 발생
장시간 실행되는 응용 프로그램/시스템 프로그램(ex. 운영체제 그 자체)에서 큰 문제임
-> 메모리 누수 -> 메모리 부족 -> 시스템 재시작할 수 밖에 없음
쓰레기 수집 기능이 있는 언어도 메모리 누수 발생
-> 메모리 청크에 대한 참조가 존재하면 쓰레기 수집기가 청크를 해제하지 않기 때문
프로그램 실행 시간이 짧아, 실행 후 곧 종료하는 경우
-> 프로세스가 죽으면, 운영체제는 할당된 페이지를 모두 정리
-> 메모리 누수 일어나지 않는다
하지만, 이와 같은 전략은 매우 조심해서 사용해야 한다
메모리 사용이 끝나기 전에 메모리 해제 (= dangling pointer)
차후 그 포인터를 사용하면 프로그램을 크래시 시키거나 유효 메모리 영역을 덮어쓸 수 있다
-> 예를 들어, free()를 호출하고, 그 후 다른 용도로 malloc()을 호출하면 잘못 해제된 메모리를 재사용한다
free는 다시 malloc으로 할당 받을 수 있게 해제하는 역할이라고 생각할 수 있다
(1) x라는 포인터에 malloc으로 10바이트를 할당
(2) y라는 포인터에 malloc으로 20바이트를 할당
-> 두 메모리는 겹치지 않는다
(1) x라는 포인터에 malloc으로 10바이트를 할당
(2) free(x)
(3) y라는 포인터에 malloc으로 20바이트 할당
-> free(x)로 해제한 메모리의 일부 또는 전부를 할당 받을 수 있다
메모리를 한 번 이상 해제
-> 이중 해제(double free) 발생
결과는 예측하기 어렵다
-> 가장 흔히 일어나는 결과: 크래시
라이브러리 구축에 사용되는 시스템 콜 중 하나
프로그램의 break 위치를 변경하는 데 사용
-> break는 힙의 마지막 위치를 나타낸다
brk는 새로운 break 주소를 나타내는 한 개의 인자를 받는다
-> 새로운 break > 현재 break: 힙의 크기 증가
-> 새로운 break < 현재 break: 힙의 크기 감소
sbrk 시스템 콜 : brk와 비슷한 용도로 사용됨
(sbrk는 증가량만을 받아들인다)
brk와 sbrk는 메모리 할당 라이브러리에 의해 사용됨
-> 직접 호출해서는 안 된다
mmap() 함수를 사용하여 운영체제로부터 메모리를 얻을 수도 있다
올바른 인자를 전달하면 mmap()는 프로그램에 anonymous의 메모리 공간을 만든다
-> anonymous 영역: 특정 파일과 연결되어 있지 않고 스왑 공간(swap space) 에 연결된 영역
calloc() 함수: 메모리 할당 영역을 0으로 채워서 반환
-> 초기화 하는 것을 잊어버리는 오류 방지
realloc() 함수: 이미 할당된 공간에 대해 추가의 공간이 필요할 때 유용 (ex. 배열)
-> 더 큰 새로운 영역을 확보 -> 옛 영역의 내용을 복사 -> 새 영역에 대한 포인터 반환