フリーキーズ | 独学プログラミング

デバッグ実践:C++のgdb/lldbでバグを素早く特定する方法

リンドくん

リンドくん

たなべ先生、C++のプログラムでセグフォって出るんですけど、どこでエラーが起きてるのか全然わからないです...

たなべ

たなべ

あー、セグメンテーションフォルトね!
確かに最初はどこで問題が起きているのかわからなくて困るよね。でも大丈夫、gdbやlldbというデバッガを使えば、エラーの場所をピンポイントで特定できるんだよ。

C++でプログラミングを学んでいると、必ず遭遇するのが「セグメンテーションフォルト」や予期しない動作などのバグです。
特に初心者の方は、エラーメッセージを見ても「一体どこで何が起きているのか」がわからず、途方に暮れることも多いのではないでしょうか?

そんな時に強力な助けとなるのが、gdb(GNU Debugger)lldb(LLVM Debugger)というデバッガツールです。
これらのツールを使いこなすことで、バグの原因を素早く特定し、効率的に修正できるようになります。

この記事では、C++のデバッグで悩んでいる初心者の方に向けて、gdbとlldbの基本的な使い方から実践的なデバッグテクニックまで、わかりやすく解説していきます。

プログラミング学習でお悩みの方へ

HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。

✓ 質問し放題

✓ β版公開中(2025年内の特別割引)

HackATAの詳細を見る

デバッガとは何か?なぜ必要なのか

リンドくん

リンドくん

そもそもデバッガって何をするツールなんですか?

たなべ

たなべ

デバッガは、プログラムの実行を制御しながら内部の状態を確認できるツールなんだ。
まるで時間を止めて、プログラムの中身を覗き見ることができる魔法のツールだと思ってもらえればいいよ。

デバッガの基本概念

デバッガとは、プログラムの実行中に以下のことができる強力なツールです。

  • プログラムの実行を一時停止して、その時点での変数の値を確認
  • 1行ずつ実行しながら、プログラムの流れを追跡
  • 特定の地点で自動停止するブレークポイントを設定
  • メモリの内容やスタックトレースを詳細に表示

これらの機能により、「なぜプログラムが思った通りに動かないのか」を詳細に分析できるのです。

printfデバッグとの違い

多くの初心者が最初に行うのが「printfデバッグ」または「coutデバッグ」と呼ばれる方法です。
これは、プログラムの各所に出力文を挿入して動作を確認する方法ですが、以下のような問題があります。

  • コードを変更する必要がある → 後でprintf, coutを削除し忘れることも
  • 大量の出力で混乱しやすい → 重要な情報を見逃すリスク
  • リアルタイムでの変数確認ができない → 動的な確認が困難

一方、デバッガを使えば、コードを一切変更することなく、必要な情報を必要な時に確認できます。

gdbとlldbの基本的な使い方

デバッグ情報付きでコンパイル

デバッガを効果的に使用するには、まずデバッグ情報付きでコンパイルする必要があります。

// 例 sample.cpp
#include <iostream>
using namespace std;

int main() {
    int* ptr = nullptr;      // どこも指していないポインタ
    cout << *ptr << endl;     // nullptr をデリファレンス → Segmentation fault
    return 0;
}

コンパイルコマンド

# gdbの場合
g++ -g -o sample sample.cpp

# lldbの場合(clang使用)
clang++ -g -o sample sample.cpp

-g オプションが重要で、これによりデバッグ情報がバイナリファイルに埋め込まれます。

デバッガの起動

# gdbの場合
gdb ./sample

# lldbの場合
lldb ./sample

基本的なコマンド一覧

デバッガで最低限覚えておきたいコマンドは以下の通りです。

プログラムの実行制御

  • run(両方共通)/ process launch(lldb) → プログラムを実行
  • continue(両方共通) → 実行を継続
  • quit(両方共通) → デバッガを終了

ブレークポイント関連

  • break 行番号(gdb)/ breakpoint set --line 行番号(lldb) → ブレークポイント設定
  • info breakpoints(gdb)/ breakpoint list(lldb) → ブレークポイント一覧表示

ステップ実行

  • step(両方共通) → 1行ずつ実行(関数内部にも入る)
  • next(両方共通) → 1行ずつ実行(関数呼び出しはスキップ)

実践的なデバッグ手順とテクニック

リンドくん

リンドくん

実際にバグが起きた時は、どういう手順でデバッグすればいいんですか?

たなべ

たなべ

いい質問だね!
効率的なデバッグには系統立ったアプローチが大切なんだ。段階的に問題を絞り込んでいく方法を教えるよ。

ステップ1 エラーの発生場所を特定

まず、プログラムがクラッシュする場合は、その場所を特定しましょう。

lldbでの例

(lldb) run
# プログラムがクラッシュした場合
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00000001000004b8 sample`main at sample.cpp:7:13
   4    int main()
   5    {
   6        int* ptr = nullptr;
-> 7        cout << *ptr << endl;
   8        return 0;
   9    }

この情報から、7行目でBAD_ACCESSが発生していることがわかります。

ステップ2 変数の値を確認

エラー発生時点での変数の状態を確認します。

# 変数の値を表示
(lldb) print ptr
(int *) 0x0000000000000000

(lldb) print *ptr
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory

ステップ3 ブレークポイントを使った詳細調査

問題の箇所にブレークポイントを設定して、段階的に実行します。

(lldb) target create "sample"
Current executable set to '/Users/kt2763/tmp/cpp/sample' (arm64).

(lldb) breakpoint set --name main # ブレイクポイント設定
Breakpoint 1: 45 locations.

(lldb) breakpoint set --file main.cpp --line 6 # 行指定も可能
Breakpoint 2: where = sample`main + 24 at sample.cpp:6:10, address = 0x00000001000004b0

(lldb) run
Process 47163 launched: '/Users/kt2763/tmp/cpp/sample' (arm64)
Process 47163 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 2.1
    frame #0: 0x00000001000004b0 sample`main at main.cpp:6:10
   3
   4    int main()
   5    {
-> 6        int* ptr = nullptr; # ここで止まる(左側に矢印が見える)
   7        cout << *ptr << endl;
   8        return 0;
   9    }

# ステップ実行しながら変数を確認
(lldb) step
Process 47163 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x00000001000004b4 sample`main at main.cpp:7:14
   4    int main()
   5    {
   6        int* ptr = nullptr;
-> 7        cout << *ptr << endl;
   8        return 0;
   9    }

(lldb) next

より高度なデバッグテクニック

条件付きブレークポイント

# iが3の時のみ停止
(lldb) breakpoint set --file main.cpp --line 10 --condition "i == 3"

ウォッチポイント

# 変数xが変更された時に停止
(lldb) watchpoint set variable x

よくあるバグパターンとデバッガでの対処法

パターン1 配列の範囲外アクセス

配列でよく発生する問題です。

症状: セグメンテーションフォルトまたは予期しない値(範囲外に変な値が入る)

パターン2 未初期化変数

int calculateSum()
{
    int result;  // 初期化されていない
    int data[] = {1, 2, 3, 4, 5};
    
    for (int i = 0; i < 5; i++)
    {
        result += data[i];  // 未初期化変数への加算
    }
    
    return result;
}

パターン3 ポインタ関連のエラー

#include <iostream>
using namespace std;

int main()
{
    int* ptr = nullptr;
    
    // nullポインタへのアクセス
    *ptr = 42;
    
    cout << "値: " << *ptr << endl;
    return 0;
}

デバッガを活用した効率的な開発ワークフロー

開発サイクルにデバッガを組み込む

効率的な開発のためには、以下のワークフローを実践することをおすすめします。

  1. コンパイル時は常に-gオプションを使用
  2. テスト実行前にデバッガで動作確認
  3. バグ発見時は即座にデバッガで原因調査
  4. 修正後はデバッガで検証

IDEとの連携

多くの統合開発環境(IDE)では、gdbやlldbとの連携機能が提供されています。

  • Visual Studio Code → C++拡張機能でgdb/lldb統合
  • CLion → 標準でデバッガ機能を搭載
  • Qt Creator → gdbとの密接な連携

これらのツールを使うことで、グラフィカルな interface でデバッガを操作でき、初心者でも扱いやすくなります。

デバッグ効率を上げるコツ

最初から完璧を目指さない

  • 小さな機能単位でテストとデバッグを繰り返す
  • 複雑なロジックを書く前に、単純な部分から動作確認

ログと組み合わせる

  • デバッガで詳細調査、ログで全体の流れを把握
  • 時系列でのデバッグ情報として活用

チーム開発での活用

  • バグレポートにデバッガの出力結果を添付
  • コードレビューでのポテンシャルなバグの指摘

まとめ

リンドくん

リンドくん

デバッガって最初は難しそうに見えましたけど、意外と使いやすそうですね!

たなべ

たなべ

そうなんだ!最初の一歩が踏み出せれば、デバッガは君の最強の味方になってくれるよ。
特にC++みたいなシステムレベルの言語では、デバッガなしの開発は考えられないからね。

C++のデバッグにおいて、gdbとlldbはプログラマにとって必須のスキルです。
最初は複雑に感じるかもしれませんが、基本的なコマンドから始めて徐々に習得していけば、必ず身につけることができます。

今回学んだ重要なポイントをまとめましょう。

  • デバッガはプログラムの内部状態をリアルタイムで確認できる強力なツール
  • コンパイル時の-gオプションがデバッグ情報埋め込みの鍵
  • ブレークポイントとステップ実行で問題箇所を効率的に特定
  • 変数の値確認とメモリ状態の把握でバグの根本原因を解明

デバッガを使いこなすことで、開発効率は格段に向上し、より高品質なC++プログラムを作成できるようになります。
「バグが怖い」から「バグも楽しい!」と思えるようになれば、あなたのプログラミングスキルは大きく飛躍するでしょう。

この記事をシェア

関連するコンテンツ