malloc 과 free 는 한 쌍의 짝으로 같이 움직인다.
malloc 은 언제 쓰는지?
스택 변수는 크기를 컴파일할 때 정해야 한다. 실행 중에 크기가 정해지거나, 함수가 끝난 뒤에도 데이터가 살아있어야 하면 힙을 써야하는데, 그게 malloc 이다.
int n;
scanf("%d", &n);
int arr[n]; // 크기 못 바꿈
malloc 기본형
int *arr = (int *)malloc(5 * sizeof(int));
5 * sizeof(int) : 필요한 바이트 수 (개수 x 한 칸 크기)
(int *) : 반환값을 int 포인터로 변환
int *arr : 받는 변수는 반드시 포인터
malloc 몇 개가 아니라 몇 바이트를 받는다. malloc(5) 는 5칸이 아니라 5바이트이다.
타입 *p = malloc(개수 * sizeof(타입));
더 안전한 버전은 sizeof 에 타입 대신 변수를 쓰는거다. 나중에 타입을 바꿔도 같이 따라간다. (유지보수성이 좋아짐)
sizeof
int *arr = malloc(5 * sizeof(*arr));
변수를 써도 안전한 이유는, sizeof 는 피연산자를 실행하지 않는다. sizeof 는 함수가 아니라 연산자이다. 괄호 안 표현식의 타입이 뭔지 보고, 그 타입의 크기를 내놓는다. 표현식을 실제로 계산하지 않는다. arr 에 실제로 접근하지는 않는다.
즉, *arr 의 타입은 → arr 가 int* 니까, *arr 는 int
malloc 은 void 를 제공하는데, int * 에 들어가는 이유는?
void * 는 C 에서 다른 객체 포인터 타입과 캐스팅 없이 서로 변환된다. C++ 이라면 에러가 나서 (int *) 를 붙여야 한다. 근데 순수 C 에서는 붙이지 않는 걸 권장한다.
- 헤더 누락을 숨기거나, 유지보수 때 실수를 만들기 때문에.
NULL 체크
메모리가 부족하면 malloc 은 NULL 을 돌려준다. 이 상태로 쓰면 바로 시스템이 터진다.
int *arr = malloc(5 * sizeof(int));
if (arr == NULL){
printf("메모리 할당 실패\n");
return 1;
}
free
다 쓰면 반납해야 한다. 안 하면 프로그램이 끝날 때까지 그 메모리는 아무도 못 쓴다.
free(arr);
arr = NULL;
댕글링 포인터
arr = NULL 이 필요한 이유는, free(p) 는 힙 블록만 반납할 뿐, p 에 적힌 주소는 건드리지 않는다. p 는 여전히 0 x 5000을 들고 있는데 그 자리는 이미 남의 것이 된 상태이다. 이것을 댕글링 포인터 라고 한다.
이 상태에서 p[0] 을 읽으면 운이 좋을 때 쓰레기 값이 나오고, 나쁘면 프로그램이 죽는다. 가끔만 터져서 디버깅하기 쉽지 않다. 그래서 free 뒤에 바로 NULL 을 넣어두면 잘못 접근했을 때 확실하게 터져서 찾기 쉽다.
흔한 실수들
- 메모리 누수 - free 를 안함
- 스택 메모리를 free
- 이중 free
- 포인터를 옮겨놓고 free
Calloc
0으로 초기화까지 해준다.
int *a = malloc(5 * sizeof(int));
int *b = calloc(5, sizeof(int)); // 0으로 초기화
calloc 을 쓰지 않고 malloc 을 더 쓰는 경우
calloc 이 0으로 초기화 해줘서 쓰레기 값이 들어가는 malloc 보다 더 사용이 선호되어야 하는 것 아닌가? 라는 의문이 들었다. 0으로 초기화 하는것도 for 문이 한번 더 돌기 때문에, 이제 이런 시간도 아끼겠다 하면 malloc 을 사용한다고 한다. 추가로 더 찾아본 것은 다른 곳에서 바로 값을 가져와 0으로 초기화 할 필요 없는 경우가 많아 malloc 사용이 더 선호된다고 한다.
Realloc
realloc 은 크기를 바꾼다. 주의해야 하는 점은 자리가 모자라면 다른 곳으로 통째로 옮기고 새 주소를 돌려준다. 그래서 반환값을 꼭 받아야 하고, 옛날 포인터는 무효가 된다.
arr = realloc(arr, 6 * sizeof(int));
전체 흐름
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *arr = malloc(n * sizeof(int)); // 1. 할당
if (arr == NULL){ // 2. 확인
printf("할당 실패\n");
return 1;
}
for (int i = 0; i < n; i++){ // 3. 사용
arr[i] = i * i;
}
printf("%d\n", arr[3]);
free(arr); // 4. 반납
arr = NULL; // 5. 무효화
return 0;
}
- malloc 한 줄마다 대응하는 free 가 어딘가에 있는가
- 크기를 개수 * sizeof(타입) 으로 썼는가
- 할당 직후 NULL 체크를 했는가
- free 뒤에 NULL 을 넣었는가