デバッグテクニック

ここでは、Qt ベースのソフトウェアのデバッグに役立つヒントをいくつか紹介します。

デバッグのための Qt の設定

Qt をインストールする際にアプリケーションやライブラリのバグを追跡しやすくするためのデバッグシンボルを含むようにビルドすることができます。しかし、プラットフォームによっては、Qt をデバッグモードでビルドすると、アプリケーションのサイズが大きくなってしまいます。

macOS と Xcode でのデバッグ

フレームワークの有無によるデバッグ

デバッグライブラリとフレームワークについて知っておくべき基本的なことは、developer.apple.com にあります:Apple Technical Note TN2124 にあります。

Qt をビルドするとき、デフォルトでフレームワークがビルドされ、フレームワークの中にはリリース版とデバッグ版の両方があります(例えば、QtCore と QtCore_debug)。Qt をビルドするときに-no-framework フラグを渡すと、各 Qt ライブラリに対して 2 つの dylib がビルドされます (例: libQtCore.4.dylib と libQtCore_debug.4.dylib)。

リンク時に何が起こるかは、フレームワークを使うかどうかによります。どちらか一方を推奨する説得力のある理由は見当たりません。

フレームワークを使う場合:

リリース・ライブラリーとデバッグ・ライブラリーはフレームワークの中にあるので、アプリはフレームワークに対してリンクされるだけです。そして、デバッガーで実行すると、DYLD_IMAGE_SUFFIX を設定したかどうかによって、リリース・バージョンかデバッグ・バージョンのどちらかが得られます。設定しない場合、デフォルトでリリース・バージョンが得られます(つまり、_debugではない)。DYLD_IMAGE_SUFFIX=_debug を設定した場合は、デバッグ・バージョンを取得します。

フレームワークなし:

qmakeにデバッグ設定付きのMakefileを生成するように指示すると、ライブラリの_debugバージョンに対してリンクし、アプリ用のデバッグシンボルを生成します。GDBでこのプログラムを実行すると、他のプラットフォームでGDBを実行するのと同じように動作し、Qt内部をトレースできるようになります。

Qt が認識するコマンドラインオプション

Qt アプリケーションを実行するとき、デバッグに役立ついくつかのコマンドラインオプションを指定できます。これらはQApplication で認識されます。

オプション説明
-nograbthe mouse the keyboardこのオプションは、プログラムが Linux のgdb デバッガで実行されている場合、デフォルトで設定されます。
-dograb暗黙的または明示的な-nograb は無視します。-nograb がコマンドラインの最後にある場合でも、-dograb-nograb より優先されます。

Qt が認識する環境変数

実行時に、Qt アプリケーションは多くの環境変数を認識します:

変数説明
QT_DEBUG_PLUGINSQtがロードしようとする各プラグイン(C++)に関する診断情報を出力するようにするには、ゼロ以外の値を設定します。
QML_IMPORT_TRACEQMLがインポート読み込みメカニズムからの診断情報を出力するようにするには、0以外の値を設定します。
QT_HASH_SEED整数値に設定すると、QHashQSet 、アプリケーションの実行ごとに新しいランダムな順序を使用します。
QT_WIN_DEBUG_CONSOLEWindowsでは、GUIアプリケーションはコンソールに接続されていないため、stdoutstderr に書き込まれた出力はユーザーには見えません。IDEは通常、出力をリダイレクトして表示しますが、コマンドラインからアプリケーションを実行する場合、デバッグ出力は失われます。出力にアクセスするには、この環境変数をnew に設定してアプリケーションに新しいコンソールを割り当てさせるか、attach に設定してアプリケーションに親プロセスのコンソールへのアタッチを試みさせます。

警告とデバッグメッセージ

Qt には、警告とデバッグ・テキストを出力するためのグローバル C++ マクロがあります。プレーンなマクロは、デフォルトのlogging category を使用します。カテゴライズされたロギング・マクロは、カテゴリを指定することができます。これらのマクロは、次のような目的で使用できます:

プレーンマクロカテゴリ指定マクロ目的
qDebug()qCDebug()カスタムデバッグ出力の記述に使用
qInfo()qCInfo()情報メッセージに使用する
qWarning()qCWarning()アプリケーションやライブラリの警告や回復可能なエラーを報告するために使用します。
qCritical()qCCritical()重大なエラーメッセージの記述やシステムエラーの報告に使用する
qFatal()-終了する直前に致命的なエラーに関するメッセージを書くときに使用します。

<QtDebug> ヘッダファイルをインクルードしている場合、qDebug() マクロも出力ストリームとして使用できます。例えば

qDebug() << "Widget" << widget << "at position" << widget->pos();

これらのマクロの Qt 実装は、Unix/X11 と macOS ではstderr に出力します。Windowsでは、コンソール・アプリケーションであればコンソールにテキストが送られ、そうでなければデバッガに送られます。

デフォルトでは、メッセージのみが出力されます。環境変数QT_MESSAGE_PATTERN を設定することで、追加情報を含めることができる。例えば

QT_MESSAGE_PATTERN="[%{time process} %{type}] %{appname} %{category} %{function} - %{message}"

フォーマットはqSetMessagePattern() に記述されている。また、qInstallMessageHandler() を使用して、独自のメッセージ・ハンドラをインストー ルすることもできる。

環境変数QT_FATAL_WARNINGS が設定されている場合、qWarning() は警告メッセージを表示した後に終了する。これにより、デバッガでバックトレースを簡単に取得できる。

qDebug(),qInfo(),qWarning() はデバッグ・ツールである。これらは、コンパイル時にQT_NO_DEBUG_OUTPUTQT_NO_INFO_OUTPUTQT_NO_WARNING_OUTPUT を定義することで、コンパイルすることができる。

デバッグ関数QObject::dumpObjectTree() とQObject::dumpObjectInfo() は、アプリケーションの見た目や動作が変な場合によく役に立ちます。object names

QMLでは、dumpItemTree()が同じ役割を果たします。

qDebug()ストリーム演算子のサポート

qDebug()で使用されるストリーム演算子を実装することで、 クラスのデバッグをサポートすることができます。ストリームを実装するクラスはQDebug です。QDebugStateSaver を使用して、ストリームのフォーマット・オプションを一時的に保存します。nospace() およびQTextStream manipulators を使用して、書式設定をさらにカスタマイズします。

以下は、2D 座標を表すクラスの例です。

QDebug operator<<(QDebug dbg, const Coordinate &c)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "(" << c.x() << ", " << c.y() << ")";

    return dbg;
}

Qtのメタオブジェクトシステムとカスタム型の統合については、「Creating Custom Qt Types」で詳しく説明しています。

マクロのデバッグ

ヘッダーファイル<QtGlobal> には、いくつかのデバッグ用マクロと#defineが含まれています。

3つの重要なマクロがあります:

  • Q_ASSERT(cond)は、cond がブール式で、"ASSERT:'cond' in file xyz.cpp, line 234 "という警告を書き、cond が偽の場合に終了します。
  • Q_ASSERT_X(cond、where、what)、cond はブール式、where は場所、what はメッセージで、警告を書き込む:"ASSERT failure inwhere: 'what', file xyz.cpp, line 234" と警告を書き、cond が偽の場合は終了する。
  • Q_CHECK_PTR(ptr)、ここでptr はポインタである。ファイル xyz.cpp、234 行目:ptr が 0 の場合、"In file xyz.cpp, line 234: Out of memory" という警告を表示し、終了します。

これらのマクロは、プログラム・エラーを検出するのに便利です:

char *alloc(int size)
{
    Q_ASSERT(size > 0);
    char *ptr = new char[size];
    Q_CHECK_PTR(ptr);
    return ptr;
}

Q_ASSERT(),Q_ASSERT_X(),Q_CHECK_PTR() は、コンパイル時にQT_NO_DEBUG が定義されていると、何も展開されない。このため、これらのマクロの引数に副作用があってはならない。以下は、Q_CHECK_PTR ()の誤った使用例である:

char *alloc(int size)
{
    char *ptr;
    Q_CHECK_PTR(ptr = new char[size]);  // WRONG
    return ptr;
}

このコードがQT_NO_DEBUG が定義された状態でコンパイルされた場合、Q_CHECK_PTR() 式のコードは実行されず、alloc は初期化されていないポインタを返します。

Qt ライブラリには何百もの内部チェックが含まれており、プログラミングエラーが検出されると警告メッセー ジが表示されます。そのため、Qt ベースのソフトウェアを開発する際には、デバッグ版の Qt を使用することをお勧めします。

QMLでもロギングやcategorized logging

よくあるバグ

よくあるバグが1つあります:クラス宣言にQ_OBJECT マクロを含めてメタオブジェクトコンパイラ(moc) を実行し、moc で生成されたオブジェクトコードを実行ファイルにリンクし忘れると、非常に紛らわしいエラーメッセージが表示されます。vtbl_vtbl__vtbl などの不足を訴えるリンクエラーは、この問題の結果である可能性が高いです。

本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。