메모리 누수
메모리 누수memory leak란, 프로그램이 필요하지 않은 메모리를 계속 사용하는 현상을 말한다. 할당된 메모리를 사용한 후 반환하지 않으면 메모리가 낭비되고, 결국 메모리 부족으로 인한 오류가 발생할 수 있다.
할당된 메모리를 사용한 다음 반환하지 않는 것이 누적되면 메모리가 낭비된다. 즉, 더 이상 불필요한 메모리가 해제되지 않으면서 메모리 할당을 잘못 관리할 때 발생한다. 이로 인해 사용 가능한 메모리가 점점 줄어들며, 장기적으로는 시스템 성능 저하, 응용 프로그램의 비정상 종료 또는 전체 시스템의 불안정성을 초래할 수 있다.
|
원인
|
이전에 할당된 메모리를 올바르게 해제하지 못함
|
|
증상
|
프로그램 성능 저하, Out of Memory Error, 프로그램 응답하지 않음, 강제 종료
|
|
해결 방법
|
힙 덤프 분석을 통해 문제 지점을 찾고 해결
|
|
방지 방법
|
해제되지 않은 리소스를 정상적으로 해제하고, static 변수가 큰 객체를 참조하지 않도록 주의
|
메모리 누수의 원인
메모리 누수는 주로 다음과 같은 상황에서 발생한다.
- 할당 후 해제하지 않음: malloc, calloc, realloc 등을 통해 메모리를 할당하고, free로 해제하지 않는 경우
- 포인터를 잃어버림: 할당받은 메모리를 가리키는 포인터가 다른 값을 참조하게 되어 기존에 할당된 메모리에 접근할 수 없게 되는 경우
- 반복적인 할당: 루프 내에서 반복적으로 메모리를 할당하고, 매번 해제하지 않는 경우
- 예외 처리 누락: 조건 분기나 오류 발생 시 free 호출 없이 함수가 종료되는 경우
C 언어에서의 메모리 누수 예시
1. 포인터를 덮어씌워서 원래 주소를 잃어버리는 경우
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int) * 5);
if (ptr == NULL) return 1;
ptr = (int *)malloc(sizeof(int) * 10); // 이전에 할당한 5개짜리 메모리 주소를 잃어버림
free(ptr); // 두 번째 할당된 것만 해제됨, 첫 번째는 누수 발생
return 0;
}
2. 조건문/에러 분기 등에서 free 누락
#include <stdio.h>
#include <stdlib.h>
int process(int *arr) {
if (arr == NULL) {
return -1;
}
// 중간에 오류가 발생했다고 가정
if (1) {
return -1; // 메모리 해제 없이 조기 종료 → 누수 발생
}
free(arr);
return 0;
}
int main() {
int *data = (int *)malloc(sizeof(int) * 100);
process(data); // 누수 발생 가능
return 0;
}
3. 루프에서 할당만 하고 해제하지 않는 경우
#include <stdio.h>
#include <stdlib.h>
int main() {
for (int i = 0; i < 100; i++) {
int *arr = (int *)malloc(sizeof(int) * 50);
// free(arr); // 주석 처리로 인해 반복적으로 메모리 누수 발생
}
return 0;
}
4. 구조체 내부 포인터 누수
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *name;
} Person;
int main() {
Person *p = (Person *)malloc(sizeof(Person));
p->name = (char *)malloc(50);
// 일부 메모리만 해제
free(p); // p->name은 해제되지 않음 → 누수 발생
return 0;
}
- 구조체 멤버도 별도로 free(p->name); 하고 나서 free(p); 해야 완전한 해제가 된다.
5. 전역 또는 정적 변수에 할당 후 해제 안 함
#include <stdlib.h>
char *global_ptr;
void allocate() {
global_ptr = (char *)malloc(100);
// 프로그램 종료 전까지 해제하지 않으면 누수로 간주될 수 있음
}
int main() {
allocate();
return 0;
}
- 이 경우는 프로그램 종료 시 OS가 회수하긴 하지만, 장시간 실행되는 프로그램에서는 실제 누수로 이어질 수 있다.
메모리 누수를 방지하는 방법
- 사용 후 반드시 free 호출: 동적으로 할당한 메모리는 사용이 끝나면 반드시 해제해야 한다.
- 포인터 관리 철저: 포인터를 덮어쓰기 전에 먼저 기존 메모리를 free한다.
- 메모리 추적 도구 사용: valgrind등의 도구를 활용하여 누수를 탐지하고 디버깅한다.
- 일관된 메모리 관리 정책: 할당과 해제를 동일한 함수 또는 모듈 내에서 관리하면 실수를 줄일 수 있다.
- 코딩 스타일 지키기: 예를 들어, 할당과 해제를 짝지어 사용하는 코드를 작성하거나, 예외 상황에서도 반드시 해제를 수행하도록 한다.
Valgrind 사용하기
// valgrind 설치 (ubuntu)
sudo apt update
sudo apt install valgrind
// hello 프로그램의 메모리 누수 확인
valgrind --leak-check=full ./hello
Valgrind가 출력하는 메시지는 대략 다음과 같다.
==12345== Memcheck, a memory error detector
==12345== LEAK SUMMARY:
==12345== definitely lost: 40 bytes in 1 blocks
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 0 bytes in 0 blocks
==12345== suppressed: 0 bytes in 0 blocks
==12345==
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x........: malloc (vg_replace_malloc.c:xxx)
==12345== by 0x........: main (hello.c:4)
- definitely lost: 확실한 메모리 누수! free되지 않고 포인터도 잃어버린 메모리
- still reachable: main 끝까지 포인터가 남아 있지만 free 안 한 경우 (일부 상황에선 괜찮지만 일반적으로 정리하는 것이 좋다)
- possibly lost: 포인터가 의심스러운 방식으로 덮어쓰여 추적이 어려운 메모리
Valgrind에서 사용할 수 있는 유용한 옵션들이 있다.
| 옵션 | 설명 |
| --leak-check=full | 누수 상세 정보 출력 |
| --track-origins=yes | 오류의 원인 추적 가능 (느리지만 유용) |
| --show-leak-kinds=all | 모든 종류의 누수 보여줌 |
| --log-file=filename.txt | 출력 결과를 파일로 저장 |
Valgrind는 메모리 누수 뿐만 아니라 Segfault가 어디에서 발생했는지 확인할 수도 있다.
메모리 누수는 C 언어와 같이 수동 메모리 관리가 필요한 언어에서 자주 발생하는 문제다. 특히 시스템 자원이 제한적인 임베디드 시스템이나 실시간 시스템에서는 더욱 치명적일 수 있다. 따라서 올바른 메모리 관리 습관과 도구의 활용은 안정적인 소프트웨어 개발에 필수적이라고 할 수 있겠다.
'Krafton Jungle > 2. Keywords' 카테고리의 다른 글
| [WEEK07] 동적 메모리 할당 (0) | 2025.04.25 |
|---|---|
| [WEEK06] AVL 트리 (0) | 2025.04.21 |
| [WEEK06] 레드 블랙 트리 (Red-Black Tree) (0) | 2025.04.17 |
| [WEEK06] 균형 탐색 트리 (0) | 2025.04.16 |
| [WEEK05] KMP법, 보이어-무어법 (0) | 2025.04.14 |