C 언어 malloc
malloc은 C 언어 표준 라이브러리에서 동적 메모리를 할당하기 위해 사용하는 함수이다. 프로그램 실행 중 필요한 크기의 메모리를 힙 영역에 할당하고, 할당된 메모리의 시작 주소를 포인터로 반환한다.
malloc은 memory allocation의 약자로, 실행 시간에 필요한 메모리를 동적으로 확보할 때 사용한다. 배열의 크기나 자료구조의 크기를 컴파일 시점에 확정할 수 없을 때 유용하다.
malloc 함수는 stdlib.h 헤더에 선언되어 있다.
#include <stdlib.h>
malloc으로 할당한 메모리는 자동으로 해제되지 않으므로, 사용이 끝나면 반드시 free 함수를 사용하여 해제해야 한다.
malloc 함수의 원형은 다음과 같다.
void *malloc(size_t size);
| 항목 | 설명 |
|---|---|
| 반환형 | void 포인터 |
| 매개변수 | 할당할 메모리 크기 |
| 단위 | 바이트 |
| 실패 시 반환값 | NULL |
malloc은 요청한 크기만큼의 메모리를 바이트 단위로 할당한다. 반환형은 `void *`이므로 어떤 자료형의 포인터로도 변환하여 사용할 수 있다.
정수 1개를 저장할 메모리를 동적으로 할당하는 예는 다음과 같다.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
if (p == NULL) {
return 1;
}
*p = 10;
printf("%d\n", *p);
free(p);
return 0;
}
위 코드에서 `malloc(sizeof(int))`는 정수 1개를 저장할 수 있는 크기의 메모리를 할당한다.
정수 10개를 저장할 배열 공간을 동적으로 할당하는 예는 다음과 같다.
int *arr = malloc(sizeof(int) * 10);
사용 예:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 10;
int *arr = malloc(sizeof(int) * n);
if (arr == NULL) {
return 1;
}
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
free(arr);
return 0;
}
malloc으로 할당한 메모리는 배열처럼 인덱스를 이용하여 접근할 수 있다.
malloc으로 할당한 메모리는 초기화되지 않는다. 즉, 할당된 공간에는 이전에 사용되던 쓰레기값이 들어 있을 수 있다.
예:
int *p = malloc(sizeof(int));
이때 `*p`의 값은 정해져 있지 않다. 사용하기 전에 직접 초기화해야 한다.
*p = 0;
메모리를 0으로 초기화하면서 할당하려면 calloc을 사용할 수 있다.
malloc과 calloc은 모두 동적 메모리를 할당하지만 초기화 여부가 다르다.
| 구분 | malloc | calloc |
|---|---|---|
| 목적 | 지정한 크기의 메모리 할당 | 지정한 개수와 크기의 메모리 할당 |
| 초기화 | 초기화하지 않음 | 0으로 초기화 |
| 매개변수 | 전체 바이트 크기 | 요소 개수와 요소 크기 |
| 예 | malloc(sizeof(int) * 10) | calloc(10, sizeof(int)) |
realloc은 이미 할당한 동적 메모리의 크기를 변경할 때 사용한다.
int *arr = malloc(sizeof(int) * 10); int *tmp = realloc(arr, sizeof(int) * 20);
realloc은 기존 데이터를 가능한 한 유지하면서 메모리 크기를 변경한다. 다만 실패할 수 있으므로 임시 포인터에 결과를 받은 뒤 확인하는 방식이 안전하다.
int *tmp = realloc(arr, sizeof(int) * 20);
if (tmp != NULL) {
arr = tmp;
}
malloc으로 할당한 메모리는 사용 후 free로 해제해야 한다.
int *p = malloc(sizeof(int));
if (p != NULL) {
*p = 10;
free(p);
}
free를 호출하지 않으면 메모리 누수가 발생할 수 있다.
malloc은 메모리 할당에 실패하면 NULL을 반환한다. 따라서 malloc의 반환값은 사용 전에 확인하는 것이 안전하다.
int *p = malloc(sizeof(int));
if (p == NULL) {
printf("memory allocation failed\n");
return 1;
}
메모리 부족 상황에서 NULL 확인 없이 포인터를 사용하면 프로그램이 비정상 종료될 수 있다.
C 언어에서는 malloc의 반환값을 명시적으로 형 변환하지 않아도 된다.
권장 예:
int *p = malloc(sizeof(int));
다음과 같이 명시적 형 변환을 하는 코드도 볼 수 있다.
int *p = (int *)malloc(sizeof(int));
다만 C에서는 malloc 반환값을 캐스팅하지 않는 방식이 일반적으로 권장된다. 헤더 파일 누락으로 인한 경고를 가릴 수 있기 때문이다.
malloc을 사용할 때는 자료형 크기를 직접 숫자로 쓰기보다 sizeof를 사용하는 것이 좋다.
좋은 예:
int *arr = malloc(sizeof(int) * n);
또는 다음처럼 포인터가 가리키는 대상의 크기를 기준으로 작성할 수도 있다.
int *arr = malloc(sizeof(*arr) * n);
이 방식은 자료형이 바뀌어도 sizeof 부분을 수정하지 않아도 되어 실수를 줄일 수 있다.
구조체도 malloc으로 동적 할당할 수 있다.
#include <stdio.h>
#include <stdlib.h>
struct Student {
int id;
double score;
};
int main(void) {
struct Student *s = malloc(sizeof(struct Student));
if (s == NULL) {
return 1;
}
s->id = 1;
s->score = 95.5;
printf("%d %.1f\n", s->id, s->score);
free(s);
return 0;
}
구조체 포인터를 통해 멤버에 접근할 때는 `->` 연산자를 사용한다.
문자열을 저장할 메모리도 malloc으로 할당할 수 있다.
char *str = malloc(sizeof(char) * 6);
문자열은 마지막에 널 문자 `\0`이 필요하므로, 필요한 문자 수보다 1바이트 더 크게 할당해야 한다.
예를 들어 "hello"는 5글자이지만 실제로는 6바이트가 필요하다.
h e l l o \0
2차원 배열은 여러 방식으로 동적 할당할 수 있다.
포인터 배열을 이용하는 방식은 다음과 같다.
int **arr = malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
arr[i] = malloc(sizeof(int) * cols);
}
해제할 때는 할당한 순서의 반대로 각 행을 먼저 해제한 뒤 포인터 배열을 해제한다.
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
malloc으로 할당되는 메모리는 일반적으로 힙 영역에 위치한다.
C 프로그램의 메모리 영역은 보통 다음과 같이 설명된다.
| 영역 | 설명 |
|---|---|
| 코드 영역 | 실행할 프로그램 코드 저장 |
| 데이터 영역 | 전역 변수와 정적 변수 저장 |
| 스택 영역 | 지역 변수, 함수 호출 정보 저장 |
| 힙 영역 | 동적 메모리 할당에 사용 |
힙 영역은 실행 중 필요한 크기의 메모리를 할당하고 해제할 수 있지만, 개발자가 직접 관리해야 한다.
- 할당 실패 여부를 확인한다.
- malloc이 NULL을 반환할 수 있으므로 사용 전에 확인한다.
- 사용 후 반드시 free한다.
- 해제하지 않으면 메모리 누수가 발생한다.
- 해제한 포인터를 다시 사용하지 않는다.
- 이미 해제된 메모리를 참조하면 use-after-free 오류가 발생한다.
- 같은 메모리를 두 번 해제하지 않는다.
- double free 오류가 발생할 수 있다.
- 필요한 크기를 정확히 계산한다.
- 크기 계산이 잘못되면 버퍼 오버플로우가 발생할 수 있다.
- 문자열에는 널 문자 공간을 포함한다.
- 문자열 길이보다 1바이트 더 크게 할당해야 한다.
- 해제 후 포인터를 NULL로 설정하면 안전성에 도움이 된다.
- 실수로 다시 사용하는 위험을 줄일 수 있다.
예:
free(p); p = NULL;
할당한 메모리를 해제하지 않으면 메모리 누수가 발생한다.
void func(void) {
int *p = malloc(sizeof(int) * 100);
if (p == NULL) {
return;
}
// free(p)가 없음
}
malloc 실패 후 NULL 포인터를 그대로 사용하면 오류가 발생한다.
int *p = malloc(sizeof(int)); *p = 10; // p가 NULL이면 오류
free한 뒤 해당 포인터를 다시 사용하는 오류이다.
int *p = malloc(sizeof(int)); free(p); *p = 10; // 잘못된 사용
같은 메모리를 두 번 해제하는 오류이다.
int *p = malloc(sizeof(int)); free(p); free(p); // 잘못된 사용
할당한 크기보다 더 많이 쓰면 버퍼 오버플로우가 발생한다.
int *arr = malloc(sizeof(int) * 3); arr[3] = 10; // 인덱스 범위 초과
| 함수 | 설명 |
|---|---|
| malloc | 지정한 바이트 크기의 메모리를 할당한다. |
| calloc | 지정한 개수와 크기의 메모리를 0으로 초기화하여 할당한다. |
| realloc | 기존 동적 메모리의 크기를 변경한다. |
| free | 동적으로 할당한 메모리를 해제한다. |
malloc은 다음과 같은 경우에 사용된다.
- 실행 중 크기가 결정되는 배열 생성
- 연결 리스트 구현
- 트리 구현
- 그래프 구현
- 동적 문자열 처리
- 파일 크기에 따른 버퍼 생성
- 자료구조의 노드 생성
- 가변 크기 데이터 처리
C 언어에서 연결 리스트, 트리, 그래프 같은 동적 자료구조는 malloc을 사용하여 노드를 생성하는 경우가 많다.
연결 리스트 노드 예:
struct Node {
int data;
struct Node *next;
};
struct Node *node = malloc(sizeof(struct Node));
동적 자료구조는 필요한 만큼 노드를 생성하고, 더 이상 필요하지 않을 때 해제할 수 있다는 장점이 있다.
malloc 사용 오류는 보안 취약점으로 이어질 수 있다.
대표적인 취약점은 다음과 같다.
안전한 사용을 위해서는 크기 검증, NULL 확인, 경계 검사, 해제 후 포인터 관리, 정적 분석 도구 활용 등이 필요하다.
| 구분 | 정적 배열 | malloc |
|---|---|---|
| 크기 결정 | 컴파일 시점 또는 선언 시점 | 실행 시점 |
| 저장 영역 | 스택 또는 데이터 영역 | 힙 영역 |
| 해제 방식 | 자동 해제 또는 프로그램 종료 시 해제 | free로 직접 해제 |
| 크기 변경 | 어려움 | realloc으로 가능 |
| 구분 | malloc | calloc |
|---|---|---|
| 초기화 | 하지 않음 | 0으로 초기화 |
| 매개변수 | 전체 바이트 수 | 요소 개수, 요소 크기 |
| 속도 | 초기화가 없어 상대적으로 빠를 수 있음 | 0 초기화 비용이 있음 |
| 구분 | malloc | realloc |
|---|---|---|
| 목적 | 새 메모리 할당 | 기존 메모리 크기 변경 |
| 기존 데이터 | 없음 | 가능한 한 유지 |
| 사용 시점 | 처음 할당할 때 | 크기를 늘리거나 줄일 때 |
C 언어에서 malloc은 포인터, 배열, 구조체, 자료구조, 메모리 관리와 연결되는 핵심 개념이다.
학습 시에는 다음 내용을 함께 이해해야 한다.
- 포인터와 주소
- 힙 영역과 스택 영역
- sizeof 사용
- NULL 반환 확인
- free를 통한 해제
- malloc과 calloc의 차이
- malloc과 realloc의 차이
- 메모리 누수
- 버퍼 오버플로우
- use-after-free
- double free
- malloc은 C 언어에서 동적 메모리를 할당하는 함수이다.
- malloc은 stdlib.h에 선언되어 있다.
- malloc의 매개변수는 할당할 바이트 수이다.
- malloc은 할당된 메모리의 시작 주소를 반환한다.
- malloc의 반환형은 void 포인터이다.
- malloc은 실패 시 NULL을 반환한다.
- malloc으로 할당된 메모리는 초기화되지 않는다.
- malloc으로 할당한 메모리는 free로 해제해야 한다.
- free하지 않으면 메모리 누수가 발생할 수 있다.
- 해제한 메모리를 다시 사용하면 use-after-free 오류가 발생할 수 있다.
- 같은 포인터를 두 번 free하면 double free 오류가 발생할 수 있다.
- 문자열 공간을 malloc으로 할당할 때는 널 문자 공간까지 고려해야 한다.
- malloc은 배열, 구조체, 연결 리스트 등 동적 자료구조 구현에 자주 사용된다.
