はじめに
Valgrindは強力なメモリデバッグツール。メモリリーク、未初期化メモリの使用、バッファオーバーフローを検出できる。
「動いてるけどなんかおかしい」ってときに使うと原因が分かることが多い。
インストール
# Ubuntu/Debian
sudo apt install valgrind
# macOS (x86のみ、ARM非対応)
brew install valgrind
# WSL (Windows Subsystem for Linux)
sudo apt install valgrind
基本的な使い方
# デバッグ情報付きでコンパイル
g++ -g -O0 program.cpp -o program
# Valgrindで実行
valgrind ./program
# 詳細なリーク情報
valgrind --leak-check=full ./program
# より詳細な情報
valgrind --leak-check=full --show-leak-kinds=all ./program
メモリリークの例と検出
単純なリーク
// leak.cpp
#include <iostream>
void memory_leak() {
int* p = new int[100];
// delete[] p を忘れた!
}
int main() {
memory_leak();
return 0;
}
出力:
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 1 allocs, 0 frees, 400 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/...)
==12345== by 0x400684: memory_leak() (leak.cpp:5)
==12345== by 0x400695: main (leak.cpp:10)
修正版
void memory_leak_fixed() {
int* p = new int[100];
// 使用...
delete[] p; // 修正!
}
// さらに良い方法: スマートポインタ
#include <memory>
void no_leak() {
auto p = std::make_unique<int[]>(100);
// 自動解放
}
未初期化メモリの使用
// uninit.cpp
#include <iostream>
int main() {
int x; // 未初期化
if (x > 0) { // UB!
std::cout << "positive" << std::endl;
}
return 0;
}
検出:
==12345== Conditional jump or move depends on uninitialised value(s)
==12345== at 0x400684: main (uninit.cpp:6)
範囲外アクセス
// overflow.cpp
#include <iostream>
int main() {
int arr[5];
arr[10] = 42; // 範囲外書き込み
return 0;
}
検出:
==12345== Invalid write of size 4
==12345== at 0x400684: main (overflow.cpp:6)
==12345== Address 0x... is 20 bytes after a block of size 20 alloc'd
解放済みメモリへのアクセス
// use_after_free.cpp
#include <iostream>
int main() {
int* p = new int(42);
delete p;
std::cout << *p << std::endl; // 解放済み!
return 0;
}
検出:
==12345== Invalid read of size 4
==12345== at 0x400684: main (use_after_free.cpp:7)
==12345== Address 0x... is 0 bytes inside a block of size 4 free'd
==12345== at 0x4C2CDFB: operator delete(void*) (...)
==12345== by 0x400680: main (use_after_free.cpp:6)
二重解放
// double_free.cpp
#include <iostream>
int main() {
int* p = new int(42);
delete p;
delete p; // 二重解放!
return 0;
}
検出:
==12345== Invalid free() / delete / delete[] / realloc()
==12345== at 0x4C2CDFB: operator delete(void*) (...)
==12345== by 0x400690: main (double_free.cpp:7)
==12345== Address 0x... is 0 bytes inside a block of size 4 free'd
Valgrindオプション
# メモリリーク詳細
valgrind --leak-check=full --show-leak-kinds=all ./program
# ログをファイルに保存
valgrind --log-file=valgrind.log ./program
# 子プロセスも追跡
valgrind --trace-children=yes ./program
# 特定のリーク種別のみ
valgrind --show-leak-kinds=definite ./program
# 抑制ファイルを使用
valgrind --suppressions=my.supp ./program
# エラー発生時にデバッガ起動
valgrind --vgdb=yes --vgdb-error=1 ./program
リークの種類
| 種類 | 説明 |
|---|---|
| definitely lost | 確実にリーク(到達不可能) |
| indirectly lost | 他のリークブロックからのみ到達可能 |
| possibly lost | リークの可能性あり |
| still reachable | まだ到達可能だが解放されていない |
Memcheck以外のツール
# Cachegrind - キャッシュプロファイラ
valgrind --tool=cachegrind ./program
# Callgrind - コールグラフ生成
valgrind --tool=callgrind ./program
kcachegrind callgrind.out.*
# Helgrind - スレッドエラー検出
valgrind --tool=helgrind ./program
# DRD - データレース検出
valgrind --tool=drd ./program
Windows/macOS代替ツール
Windows
- Visual Studio: メモリ診断ツール
- Dr. Memory: Valgrind代替
- AddressSanitizer: MSVC/Clang対応
macOS (ARM)
- Instruments: Xcode付属
- AddressSanitizer: Clang対応
AddressSanitizer (ASan)
Valgrindより高速な代替:
# コンパイル
g++ -fsanitize=address -g program.cpp -o program
# 実行(通常通り)
./program
デバッグのベストプラクティス
-
デバッグビルドで実行:
-g -O0 -
全てのリークを確認:
--leak-check=full - 定期的に実行: CIに組み込む
- スマートポインタを使う: リークを防止
- RAII: リソース管理の基本
CI/CDへの統合
# .gitlab-ci.yml
test:
script:
- g++ -g program.cpp -o program
- valgrind --error-exitcode=1 --leak-check=full ./program
まとめ
Valgrindは C/C++ のメモリバグを検出する必須ツールです。開発中に定期的に使用することで、メモリリークやバグを早期に発見できます!