C++ Valgrind | "메모리 디버깅" 가이드

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;
}

정리

핵심 요약

  1. Valgrind: 메모리 디버깅 도구
  2. Memcheck: 메모리 누수 탐지
  3. Cachegrind: 캐시 프로파일링
  4. Helgrind: 스레드 오류 탐지
  5. 성능: 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·메모리 누수·멀티스레드 디버깅 실전