Quick Flow
메모리 오류는 증상 줄이 아니라 크기·수명·소유권 중 어떤 계약이 깨졌는지부터 분류합니다. 재현한 뒤 sanitizer가 지목한 최초의 잘못된 접근을 추적합니다.
- 최소 입력으로 재현한 뒤, 인덱스·길이·capacity가
i < n계약을 지키는지 봅니다. - 할당한 곳, 소유자를 넘긴 곳,
free한 곳을 같은 allocation 단위로 연결합니다. - sanitizer가 보고한 첫 잘못된 읽기·쓰기 또는 해제를 원인으로 추적합니다.
// gcc -fsanitize=address -g -o prog prog.c
int *arr = malloc(5 * sizeof(int));
arr[5] = 99; // ASan이 바로 잡아내기 좋은 버그
free(arr);ASan은 경계 초과와 use-after-free에 특히 유용하고, LSan은 누수를 찾습니다. 도구가 경고하지 않았다는 사실만으로 모든 undefined behavior가 사라지는 것은 아닙니다.
버그 유형
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라는 식으로 역할을 나눠 보는 편이 좋습니다.
UBSan은 일부 정의되지 않은 동작을 별도로 탐지할 수 있지만, 어떤 도구도 모든 UB·논리 오류·운영 환경의 수명 문제를 증명하지는 않습니다. 증상이 사라져도 최소 재현을 유지한 뒤 수정 전후에 같은 도구와 테스트를 다시 실행합니다.
빠른 점검
- segfault다: NULL/경계/use-after-free 먼저 의심
- 값이 이상하다: 경계 초과 의심
- 해제 후 접근했다: use-after-free
- 두 번 해제했다: double free
- 반환 경로가 많다: 누수 점검
- 빠른 재현용 도구가 필요하다: ASan
- 누수 추적이 필요하다: Valgrind
주의할 점
메모리 버그는 발생 위치와 증상 위치가 다를 수 있습니다. 터진 줄만 고치면 같은 버그가 다른 곳에서 다시 나타날 수 있습니다.
char name[4];
strcpy(name, "Alexander"); // 실제 원인이후 전혀 다른 코드에서 score가 깨지거나 segfault가 날 수 있습니다. 항상 소유권, 크기, 수명을 같이 추적하세요.
참고 링크
3 sources