메모리 버그는 증상보다 원인 패턴으로 분류하면 훨씬 빨리 좁혀집니다.
C 메모리 버그는 증상 위치와 원인 위치가 다를 수 있어서, 유형별로 분류해서 보는 편이 빠릅니다.
- 경계 초과
- use-after-free
- double free
- 누수
- 초기화되지 않은 메모리 사용
숏컷 코드
// gcc -fsanitize=address -g -o prog prog.c
int *arr = malloc(5 * sizeof(int));
arr[5] = 99; // ASan이 바로 잡아내기 좋은 버그
free(arr);버그 유형
segfault가 나는 줄만 쳐다보면 원인을 놓치기 쉽습니다. 메모리 버그는 보통 증상보다 유형으로 먼저 좁히는 편이 빠릅니다.
크래시는 "바로 그 줄이 원인"이 아닐 수 있습니다. 직전 포인터 유효성, 배열 경계, 이미 해제된 메모리 접근 여부를 먼저 의심하는 편이 좋습니다.
char name[4];
strcpy(name, "Alexander"); // 경계 초과버퍼를 조금 넘겨 썼을 때는 바로 죽지 않고, 옆 변수나 힙 메타데이터를 망가뜨린 뒤 나중에 증상이 나오는 경우가 많습니다.
추적 흐름
해제 후 다시 쓰면 use-after-free, 같은 포인터를 다시 해제하면 double free입니다. 둘 다 "포인터 수명 관리 실패"로 보는 편이 좋습니다.
int *p = malloc(sizeof(*p));
free(p);
/* *p = 10; */ // use-after-free정상 흐름보다 오류 처리와 early return에서 free가 빠지기 쉽습니다.
즉 함수마다 "모든 경로에서 정리가 되는가"를 끝까지 보는 습관이 중요합니다.
확인 순서
AddressSanitizer는 오버런, UAF, double free를 빠르게 잡는 데 강하고, Valgrind는 누수와 초기화되지 않은 메모리 추적에 강합니다. 개발 중에는 ASan, 정리 단계에서는 Valgrind라는 식으로 역할을 나눠 보는 편이 좋습니다.
체크포인트
- segfault다: NULL/경계/use-after-free 먼저 의심
- 값이 이상하다: 경계 초과 의심
- 해제 후 접근했다: use-after-free
- 두 번 해제했다: double free
- 반환 경로가 많다: 누수 점검
- 빠른 재현용 도구가 필요하다: ASan
- 누수 추적이 필요하다: Valgrind
주의할 점
메모리 버그는 발생 위치와 증상 위치가 다를 수 있습니다. 터진 줄만 고치면 같은 버그가 다른 곳에서 다시 나타날 수 있습니다.
char name[4];
strcpy(name, "Alexander"); // 실제 원인이후 전혀 다른 코드에서 score가 깨지거나 segfault가 날 수 있습니다. 항상 소유권, 크기, 수명을 같이 추적하세요.
참고 링크
2 sources