C++ Valgrind | "메모리 디버깅" 가이드
이 글의 핵심
C++ Valgrind에 대한 실전 가이드입니다.
들어가며
Valgrind는 C/C++ 프로그램의 메모리 누수, 버그, 프로파일링을 위한 강력한 도구입니다. Linux/macOS에서 메모리 문제를 찾는 데 필수적입니다.
누수 개념·패턴은 메모리 누수 가이드와, AddressSanitizer·워크플로는 누수·ASan 실전에서 함께 다룹니다. 원인 예방은 스마트 포인터·RAII·이동 의미론 쪽 설계와 연결되고, Rust 소유권은 컴파일 타임 검증이라는 다른 축의 참고가 됩니다.
1. Valgrind 설치 및 기본 사용
설치
# Ubuntu/Debian
sudo apt-get install valgrind
# macOS
brew install valgrind
# 버전 확인
valgrind --version
기본 사용
# 컴파일 (디버그 정보)
g++ -g program.cpp -o program
# Valgrind 실행
valgrind --leak-check=full ./program
# 상세 정보
valgrind --leak-check=full --show-leak-kinds=all ./program
# 추적 정보
valgrind --track-origins=yes ./program
2. 메모리 누수 탐지
예제 1: 메모리 누수
// leak.cpp
#include <iostream>
int main() {
int* ptr = new int(42);
std::cout << *ptr << std::endl;
return 0;
}
g++ -g leak.cpp -o leak
valgrind --leak-check=full ./leak
출력:
==12345== HEAP SUMMARY:
==12345== in use at exit: 4 bytes in 1 blocks
==12345== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==12345==
==12345== 4 bytes in 1 blocks are definitely lost
==12345== at 0x4C2E0EF: operator new(unsigned long)
==12345== by 0x400B2C: main (leak.cpp:5)
예제 2: 초기화되지 않은 메모리
// uninit.cpp
#include <iostream>
int main() {
int x;
if (x > 0) {
std::cout << "양수" << std::endl;
}
return 0;
}
g++ -g uninit.cpp -o uninit
valgrind --track-origins=yes ./uninit
출력:
==12345== Conditional jump or move depends on uninitialised value(s)
==12345== at 0x400B2C: main (uninit.cpp:6)
예제 3: 잘못된 메모리 접근
// invalid.cpp
#include <iostream>
int main() {
int arr[10];
for (int i = 0; i <= 10; ++i) {
arr[i] = i;
}
return 0;
}
g++ -g invalid.cpp -o invalid
valgrind ./invalid
출력:
==12345== Invalid write of size 4
==12345== at 0x400B2C: main (invalid.cpp:7)
3. Valgrind 도구
Memcheck
valgrind --tool=memcheck ./program
Cachegrind
valgrind --tool=cachegrind ./program
cg_annotate cachegrind.out.12345
Callgrind
valgrind --tool=callgrind ./program
kcachegrind callgrind.out.12345
Helgrind
valgrind --tool=helgrind ./program
Massif
valgrind --tool=massif ./program
ms_print massif.out.12345
4. 자주 발생하는 문제
문제 1: 성능
# Valgrind는 매우 느림 (10-50x)
# 개발/테스트에만 사용
# 작은 입력으로 테스트
valgrind --leak-check=full ./program < small_input.txt
문제 2: 거짓 양성
# 억제 파일 생성
valgrind --gen-suppressions=all ./program > my.supp
# 억제 파일 사용
valgrind --suppressions=my.supp ./program
my.supp 예시:
{
<system_library_leak>
Memcheck:Leak
fun:malloc
fun:system_function
}
문제 3: 디버그 정보
# ❌ 디버그 정보 없음
g++ program.cpp -o program
valgrind ./program
# ✅ -g 플래그
g++ -g program.cpp -o program
valgrind ./program
문제 4: 최적화
# ❌ 최적화 높음
g++ -O3 -g program.cpp
# ✅ 최적화 낮음
g++ -O0 -g program.cpp
5. 출력 해석
누수 종류
# 확실한 누수
definitely lost: 100 bytes in 5 blocks
# 간접 누수
indirectly lost: 50 bytes in 2 blocks
# 가능한 누수
possibly lost: 20 bytes in 1 blocks
# 여전히 도달 가능
still reachable: 30 bytes in 3 blocks
해석 가이드
| 종류 | 의미 | 조치 |
|---|---|---|
| definitely lost | 확실한 누수 | 반드시 수정 |
| indirectly lost | 간접 누수 | 부모 누수 수정 |
| possibly lost | 가능한 누수 | 확인 필요 |
| still reachable | 도달 가능 | 프로그램 종료 시 해제 |
6. 실전 예제
예제 1: 스마트 포인터 사용
// smart_ptr.cpp
#include <memory>
#include <iostream>
int main() {
auto ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl;
return 0;
}
g++ -g smart_ptr.cpp -o smart_ptr
valgrind --leak-check=full ./smart_ptr
출력:
==12345== HEAP SUMMARY:
==12345== in use at exit: 0 bytes in 0 blocks
==12345== total heap usage: 1 allocs, 1 frees, 4 bytes allocated
==12345==
==12345== All heap blocks were freed -- no leaks are possible
예제 2: 벡터 누수
// vector_leak.cpp
#include <vector>
#include <iostream>
int main() {
std::vector<int*> vec;
for (int i = 0; i < 5; i++) {
vec.push_back(new int(i));
}
return 0;
}
g++ -g vector_leak.cpp -o vector_leak
valgrind --leak-check=full ./vector_leak
수정:
// vector_fixed.cpp
#include <vector>
#include <iostream>
int main() {
std::vector<int*> vec;
for (int i = 0; i < 5; i++) {
vec.push_back(new int(i));
}
for (auto ptr : vec) {
delete ptr;
}
return 0;
}
정리
핵심 요약
- Valgrind: 메모리 디버깅 도구
- Memcheck: 메모리 누수 탐지
- Cachegrind: 캐시 프로파일링
- Helgrind: 스레드 오류 탐지
- 성능: 10-50배 느림
Valgrind 도구 비교
| 도구 | 용도 | 성능 영향 |
|---|---|---|
| Memcheck | 메모리 오류 | 10-50x |
| Cachegrind | 캐시 분석 | 20-100x |
| Callgrind | 호출 그래프 | 20-100x |
| Helgrind | 스레드 오류 | 20-50x |
| Massif | 힙 분석 | 20x |
실전 팁
-g플래그로 컴파일--leak-check=full사용- 작은 입력으로 테스트
- 억제 파일로 거짓 양성 제거
- 스마트 포인터 사용
다음 단계
- C++ Sanitizers
- C++ GDB
- C++ Memory Management
관련 글
- C++ 디버깅 완벽 가이드 | GDB·Sanitizer·메모리 누수·멀티스레드 디버깅 실전