메모리를 할당한 후에 저장한 값이 필요 없어지고 나면 어떻게 해야 할까?
유한한 메모리를 효과적으로 관리하기 위해 할당한 메모리를 어떻게 관리해야 하는지 알아보자.
메모리 해제의 필요성
malloc 함수를 이용하여 메모리를 할당한 후에는 free 함수를 이용하여 메모리를 해제해야 한다.
그렇지 않으면 메모리에 저장한 값은 쓰레기 값으로 남게 되어 메모리 용량의 낭비가 발생하는데, 이러한 현상을 메모리 누수(Memory Leak)라고 한다.
문제가 있는 코드
다음 코드를 살펴보자.
#include <stdlib.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void)
{
f();
return 0;
}
- 함수
f는 포인터x에int형의 크기(4바이트) × 10 = 40바이트 메모리를 할당 x의 10번째 값으로 0을 할당
이 코드를 valgrind로 검사하면 버퍼 오버플로우와 메모리 누수에 관한 에러를 확인할 수 있다.
valgrind란?
리눅스 기반의 오픈소스 DBI 도구로, 소프트웨어의 메모리 사용을 분석하고 메모리 누수를 감지하는 프로그래밍 도구다.
문제점 1: 버퍼 오버플로우
int *x = malloc(10 * sizeof(int)); // 10개의 int형 배열 생성
x[10] = 0; // 배열의 11번째에 접근 → 오류 발생
배열의 인덱스는 0부터 9까지만 존재하는데, 존재하지 않는 11번째 인덱스(x[10])에 접근하려고 하여 버퍼 오버플로우(Buffer Overflow)가 발생한다.
메모리 상황:
할당된 메모리: x[0] x[1] x[2] ... x[9]
↓
접근 시도: x[10] ← 범위 초과!
문제점 2: 메모리 누수
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[10] = 0;
// free(x)가 없음!
}
포인터 x를 통해 할당한 메모리를 해제하지 않아 메모리 누수가 발생한다.
메모리 누수 과정:
1. f() 호출 → 메모리 할당 (40바이트)
2. f() 종료 → x 포인터는 사라짐
3. 할당된 40바이트는 여전히 메모리에 남음
4. 접근할 방법이 없는 쓰레기 메모리가 됨
함수가 여러 번 호출되면 메모리 누수가 누적된다
f() 호출 1회 → 40바이트 누수
f() 호출 100회 → 4000바이트 누수
f() 호출 100만회 → 약 38MB 누수
수정된 코드
위의 오류를 해결한 코드는 다음과 같다.
#include <stdlib.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[9] = 0; // 유효한 인덱스로 변경
free(x); // 메모리 해제
}
int main(void)
{
f();
return 0;
}
x[10]→x[9]: 유효한 인덱스 사용free(x)추가: 메모리 해제
malloc과 free 사용 패턴
기본 패턴
// 1. 메모리 할당
int *ptr = malloc(size);
// 2. NULL 체크
if (ptr == NULL)
{
return 1; // 할당 실패
}
// 3. 메모리 사용
// ... 작업 수행 ...
// 4. 메모리 해제
free(ptr);
실전 예제
#include <stdlib.h>
#include <string.h>
int main(void)
{
// 문자열 복사
char *source = "Hello";
char *copy = malloc(strlen(source) + 1);
if (copy == NULL)
{
return 1;
}
strcpy(copy, source);
// 사용 후 해제
free(copy);
return 0;
}
메모리 관리 규칙
규칙 1: malloc과 free는 짝
malloc → 반드시 free
규칙 2: 이중 해제 금지
free(ptr);
free(ptr); // ❌ 위험!
규칙 3: 해제 후 사용 금지
free(ptr);
ptr[0] = 1; // ❌ 위험! (dangling pointer)
규칙 4: NULL 체크
int *ptr = malloc(size);
if (ptr == NULL)
{
// 에러 처리
}
valgrind 사용법
# 컴파일
gcc -g memory.c -o memory
# valgrind로 검사
valgrind ./memory
// 출력 예시
==1234== Invalid write of size 4
==1234== at 0x...: f (memory.c:6)
==1234== 40 bytes in 1 blocks are definitely lost
연습 문제
Q. 다음 코드의 문제점을 찾고 수정하시오.
void process_data(void)
{
int *data = malloc(100 * sizeof(int));
data[100] = 42;
if (data[0] == 0)
{
return;
}
free(data);
}
문제점:
- 버퍼 오버플로우
data[100] = 42; // 인덱스는 0~99까지만 유효- 조건부 메모리 누수
if (data[0] == 0) { return; // free(data) 실행 안 됨 }
수정된 코드:
void process_data(void)
{
int *data = malloc(100 * sizeof(int));
// NULL 체크 추가
if (data == NULL)
{
return;
}
// 유효한 인덱스 사용
data[99] = 42;
// 조건문 전에 메모리 해제 또는
// 모든 경로에서 해제 보장
if (data[0] == 0)
{
free(data); // 해제 추가
return;
}
free(data);
}
더 나은 방법 (Early Exit 패턴):
void process_data(void)
{
int *data = malloc(100 * sizeof(int));
if (data == NULL)
{
return;
}
data[99] = 42;
if (data[0] != 0)
{
// 작업 수행
}
// 모든 경로가 여기로 도달
free(data);
}
핵심
- 모든 할당은 반드시 해제
- 유효한 인덱스만 접근
- 모든 실행 경로에서 메모리 해제 보장
'TIL' 카테고리의 다른 글
| [CS50] 메모리 - 파일 입출력 (0) | 2025.11.08 |
|---|---|
| [CS50] 메모리 - 메모리 교환, 스택, 힙 (0) | 2025.11.08 |
| [CS50] 메모리 - 문자열 복사 (0) | 2025.11.08 |
| [CS50] 메모리 - 문자열 비교 (0) | 2025.11.07 |
| [CS50] 메모리 - 문자열과 메모리 (0) | 2025.11.07 |
