[WEEK05] 동적 메모리 할당
💡 정적 메모리 할당 vs 동적 메모리 할당
- 정적 메모리 할당 (Static Memory Allocation)
- 컴파일 타임에 메모리 크기와 위치가 정해짐.
- 예)
int arr[10];처럼 크기가 고정된 배열 선언
- 동적 메모리 할당 (Dynamic Memory Allocation)
- 런타임에 메모리를 요청하고 해제하는 방식.
- 프로그램 실행 중에 메모리를 유연하게 사용할 수 있음.
🛠️ 왜 동적 메모리가 필요할까?
- 사용자 입력이나 외부 데이터에 따라 필요한 메모리 크기가 달라질 수 있기 때문.
- 메모리를 유동적으로 쓰면 낭비를 줄일 수 있음.
- 구조체나 연결 리스트, 트리 같은 동적 자료구조를 만들 때 필수적임
📌 C 언어에서 사용하는 함수들
| 함수 | 설명 |
| malloc(size) | 지정한 크기만큼 메모리 할당, 초기화 X |
| calloc(n, size) | n개의 요소를 size 크기로 할당, 0으로 초기화 |
| realloc(ptr, new_size) | 기존 메모리 블록 크기 변경 |
| free(ptr) | 할당된 메모리 해제 |
int* ptr = (int*)malloc(sizeof(int) * 5); // int 5개 공간 동적 할당
if (ptr == NULL) {
printf("메모리 할당 실패\n");
}
...
free(ptr); // 꼭 할당 해제해줘야 함!
⚠️ 주의할 점
- 할당한 메모리는 반드시 free()로 해제해야 한다 : 안 하면 메모리 누수(memory leak) 발생
- 해제 후 포인터는 NULL로 초기화하는 습관 가지기
- malloc 등의 결과는 널 포인터 체크 필수!
💻 예시: 사용자 입력 크기에 따라 배열 생성
#include <stdio.h>
#include <stdlib.h> // malloc, free
int main() {
int n;
printf("몇 개의 정수를 입력하시겠습니까? ");
scanf("%d", &n);
// 정수 n개를 저장할 수 있는 동적 배열 할당
int* numbers = (int*)malloc(n * sizeof(int));
if (numbers == NULL) {
printf("메모리 할당 실패\n");
return 1;
}
// 사용자 입력 받기
for (int i = 0; i < n; i++) {
printf("숫자 %d 입력: ", i + 1);
scanf("%d", &numbers[i]);
}
// 평균 계산
int sum = 0;
for (int i = 0; i < n; i++) {
sum += numbers[i];
}
double average = (double)sum / n;
printf("평균: %.2f\n", average);
// 동적 메모리 해제
free(numbers);
numbers = NULL; // 포인터 초기화 (안전하게)
return 0;
}
🔍 메모리 누수를 자동으로 체크해주는 툴
메모리 누수는 동적 메모리를 malloc() 등으로 할당한 후 free()를 호출하지 않아 사용하지 않는 메모리가 해제되지 않고 남아있는 상황을 말한다. 이를 자동으로 찾아주는 도구들이 있다.
✅ 대표적인 툴들
| 툴 이름 | 설명 |
| Valgrind | 가장 널리 쓰이는 메모리 디버깅 도구. 메모리 누수, 잘못된 접근, 초기화되지 않은 메모리 사용 등을 잡아줌. |
| AddressSanitizer (ASan) | GCC/Clang에서 제공하는 컴파일 옵션. 빠르고 가볍게 메모리 문제를 탐지 가능. |
| Dr. Memory | Windows에서 사용할 수 있는 Valgrind와 유사한 도구. |
| Visual Studio CRT Debugging | Windows 개발환경에서는 Visual Studio의 디버깅 기능을 통해 메모리 누수를 추적 가능. |
💡 Valgrind 사용 예시
valgrind --leak-check=full ./my_program
출력 결과로 어느 위치에서 메모리 누수가 발생했는지 알려준다고 한다.
🛡️ 동적 메모리를 더 안전하게 쓰는 방법
C 언어처럼 메모리를 수동으로 관리하는 언어에서는 실수를 방지하기 위한 몇 가지 패턴이나 팁이 있다.
1. 할당 후 NULL 체크하기
int* ptr = malloc(sizeof(int) * 10);
if (ptr == NULL) {
// 에러 처리
}
2. 사용 후 반드시 free()
할당과 해제를 세트로 생각해야 한다.
free(ptr);
ptr = NULL; // dangling pointer 방지
3. 구조체 기반의 메모리 관리
구조체에 할당 정보를 함께 넣어서 하나의 단위로 관리하는 것도 안전한 방법이다.
4. RAII (C++)
C++에서는 RAIIResource Acquisition Is Initialization 패턴을 쓰면 객체의 생성과 소멸 시 메모리를 자동으로 관리할 수 있다. (예: std::vector, std::unique_ptr, std::shared_ptr 사용)
5. 스마트 포인터 (C++)
std::unique_ptr<int> ptr(new int);
// 자동 해제됨
6. 라이브러리 사용
- GLib (for C): 안전한 메모리 관리 함수 제공
- Boehm GC: C에서도 사용할 수 있는 가비지 컬렉터 라이브러리
int *ptr; 로 포인터를 선언한 이후에도 ptr은 아직은 아무것도 가리키지 않는다. 여기서 우리는 둘 중에 하나를 선택할 수 있다.
- 이미 존재하는 무언가를 가리키게 만들기
- 포인터가 가리킬 새로운 무언가를 위한 메모리 공간을 할당하기
int *ptr, var, var2;
var = 5;
ptr = &var;
var2 = *ptr;
위의 코드는 첫 번째 경우에 대한 예시이다. 여기서 var과 var2는 그것들을 위해 암묵적으로 할당된 공간을 가지고 있다.
포인팅할 새로운 무언가를 위한 공간을 할당하기 위해서는 malloc 함수를 사용해야 한다. (보통 typecast와 sizeof와 함께 쓰인다.)
ptr = (int *) malloc (sizeof(int));
이제 ptr은 메모리 내에서 크기가 sizeof(int)) 바이트인 어떤 공간을 가리킨다.
(int *)는 단순히 컴파일러에게 어떤 자료형(여기서는 int)이 이 공간에 들어갈 지 알려준다. (typecast라고 부른다.)
malloc이 호출되면, 그 메모리 장소는 어떤 것이든지 담을 것이기 때문에 그것의 값을 지정하기 전까지는 사용하면 안 된다.
동적으로 공간을 할당한 이후에, 우리는 그것을 동적으로 해방(free) 시켜야한다.
free (ptr);
이 커맨드는 clean up할 때 사용하면 된다.
- Dynamic memory allocation
- Obtain and release memory during execution
- malloc
- Takes number of bytes to allocate
- Use sizeof to determine the size of an object
- Returns pointer of type void *
- A void * pointer may be assigned to any pointer
- If no memory available, returns NULL
- Example
- newPtr = malloc(sizeof(struct node));
- Takes number of bytes to allocate
- free
- Deallocates memory allocated by malloc
- Takes a pointer as an argument
- free(newPtr);
[참고자료]
[C언어] 동적 메모리 할당 개념 잡기
동적 메모리 할당이란 컴퓨터 프로그래밍에서 실행 중(런타임)에 사용할 메모리 공간을 할당하는 것을 의미한다.프로그램이 실행되기 위해서는 메모리가 필요하다. 컴파일러는 컴파일 시점에
velog.io