ホーム

OFF-SOFT.net

OFF-SOFT.net

ウェブやソフトウェアに関するサポート&情報サイトです。サイト構築からソフトウェアの作成、利用まであなたの助けになるかも・・・・しれません。たぶん・・。

QtアプリをVC++ 2008 Expressでデバッグする

公開日| 2009年05月11日 | コメントはまだありません。
概要 :
 Qtの環境設定について、先日、記事を書きました。(以降の関連記事を参照)
 それにより、ある程度、VC++でのQt開発ができるような環境がそろいました。 では、実際ににQtでアプリケーションを作成していくと、最初の疑問は、どうやってデバッグしようか?と思われるのではないでしょうか。 実際、今までMFCなどでアプリケーションを作成したきたプログラマーにとって、環境のレベルダウンは否めません。 MFCなどを使ったアプリケーション作成では、アプリケーションウィザードが付いていて、それに従って、雛形を作成するれば、 その雛形の中にある程度デバッグに必要なマクロなどは、埋め込まれているからです。(残念ながらQtでは、それを自身で埋めこまなければなりません。)
 とは申しても、MFCの経験のある方は、非常になじみのあるようなマクロ名で追加していけばよく、メモリリークでも、 MFCや通常のWin32アプリケーションで使用しているメモリリーク検査を使えなくはありません。

 その辺を、今回は記述してみようと思います。

関連記事: VC++ 2008 Expressを使ってQtの環境設定とサンプルコンパイル
関連記事: qmake でVC++ 2008 Expressのプロジェクトファイルを作成する

参考記事: Debugging Techniques

ここで使用したサンプルソースコード:


Qtのデバッグマクロとメッセージ出力
Qtのデバッグ用マクロとメッセージ出力は以下のものがあります。
マクロ名機能
Q_ASSERT(cond) : (*1) cond は boolean値(判定結果) です。 cnnd が FALSE であれば、次の警告を出力します :
"ASSERT: 'cnnd' in file xyz.cpp (234)"
Q_ASSERT_X(cond, where, what) : (*1)cond は上記と同じです。 whereは、出力する "どこで" という情報です。whatは、出力する "なにが" という情報になります。次の警告を出力します :
"ASSERT failure in where: 'what', file xyz.cpp, line 234"
Q_CHECK_PTR(ptr) : (*1) ptr はポインタです。 ptr が null であれば、次の警告を出力します :
"In file xyz.cpp, line 234: Out of memory"
qDebug() : (*2) テストなどのためのデバッグ出力を表示します。
qWarning() : (*3) プログラムでエラーが起きたときに警告を出力します。
qCritical() 重要なエラー(システムエラーなど)が起きたときにエラーメッセージを出力します。
qFatal() : 致命的エラーメッセージを出力し終了します。

上記のQtマクロに相当するMFCマクロ名を以下に記述します。
Qtマクロ名MFCマクロ名
Q_ASSERT(cond) : (*1) ASSERT(cond)
Q_ASSERT_X(cond, where, what) : (*1) ASSERT(cond) + TRACE()
Q_CHECK_PTR(ptr) : (*1) ASSERT_VALID(pOb) or ASSERT_POINTER(p, type)
qDebug() : (*2) TRACE() or AfxDump()
qWarning() : (*3) TRACE() or AfxDump() or fprinf(error)
qCritical() TRACE() or AfxDump() or FormatMessage ? or fprinf(error)
qFatal() : TRACE() + FatalExit/FatalAppExit

*1:QT_NO_DEBUGが定義されている場合(Release版など)は、何もしません。
*2:QT_NO_DEBUG_OUTPUTが定義されている場合(Release版など)は、何もしません。
*3:QT_NO_DEBUG_OUTPUTQT_NO_WARNING_OUTPUTが定義されている場合(Release版など)は、何もしません。

また、各メッセージ出力は、#include <QDebug> をインクルードすることで以下のような記述を行うことができます。
※通常のメッセージ出力は、printfの引数を同じになります。

1
	qDebug() << "Debug << " << &hello << "Position:" << hello.pos();

メッセージの出力先は、VC++では、デバッグ実行で、デバッグコンソール(出力画面のデバッグ)へ出力されますから、MFCと同じです。

前回の記事 "VC++ 2008 Expressを使ってQtの環境設定とサンプルコンパイル" で用いた sample.cpp を使って実際にデバッグ情報出力などを行ってみましょう。

デバッグマクロを使ってみましょう
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "QApplication"
#include "QPushButton"
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QPushButton hello("Hello world");
    hello.resize(100, 30);
 
	int ntest=10;
	int *pttest=NULL;
 
	Q_ASSERT(ntest==10);
	Q_ASSERT(ntest!=10);
	Q_ASSERT_X(ntest!=10,"this is ntest!=10."," ? I do't know.");
	Q_CHECK_PTR(pttest);
 
	hello.show();
    return app.exec();
}

上記のソースコードをデバッグ版のコンパイルを実施して、デバッグ実行してみてください。
14,15行目で、以下の画面が表示されると思います。
ここでは、デバッグ情報の確認をしたので"無視"をクリックしてください。


VC++のデバッグ出力情報に以下のような文字列が出力されたのではないでしょうか。
ASSERT: "ntest!=10" in file .\sample.cpp, line 14
ASSERT failure in this is ntest!=10.: " ? I do't know.", file .\sample.cpp, line 15
In file .\sample.cpp, line 16: Out of memory

13行目は、正しいので、スルーして、14,15,16行目でエラー情報を吐き出しています。

また、リリース版のコンパイルを実施して、デバッグ実行してみてください。
何も出力されずに、ブレイクすることなく正常に"Hello world"のボタンが表示されたと思います。

MFCと同様、リリース版では、ノーチェックになることに注意してください。

メッセージ出力を使ってみましょう
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifdef QT_NO_DEBUG
#define QT_NO_DEBUG_OUTPUT
//#define QT_NO_WARNING_OUTPUT
#endif
//	qDebug() << "Debug" のような記述を行う場合のみincludeする
#include <QDebug>
 
#include "QApplication"
#include "QPushButton"
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QPushButton hello("Hello world");
    hello.resize(100, 30);
 
	//	デバッグメッセージを出力する
	qDebug("test-Debug");
	qWarning("test-Warning");
	qCritical("test-Critical");
	qFatal("test-Fatal");
 
//	必ず、QT_NO_DEBUG_OUTPUTをチェックする(コンパイルエラーになる)
#ifndef QT_NO_DEBUG_OUTPUT
	qDebug() << "Debug << " << &hello << "Position:" << hello.pos();
#endif
 
	hello.show();
    return app.exec();
}

1 - 7行目まで追加されていることに注意してください。
1 - 4行目までは、リリース版で、デバッグ/警告メッセージ出力を行うか否かです。

2行目でリリース版で、デバッグメッセージ出力を行わないと宣言しています。
3行目(コメントアウトしていますが)でリリース版で、警告メッセージ出力を行わないと宣言しています。


上記のソースコードをデバッグ版のコンパイルを実施して、デバッグ実行してみてください。
21行目で、以下の画面が表示されると思います。
ここでは、デバッグ情報の確認をしたので"無視"をクリックしてください。


VC++のデバッグ出力情報に以下のような文字列が出力されたのではないでしょうか。
test-Debug
test-Warning
test-Critical
test-Fatal
Debug <<  QPushButton(0x12feb4) Position: QPoint(0,0) 

また、リリース版のコンパイルを実施して、デバッグ実行してみてください。
ブレイクすることなく、すぐにアプリケーションが終了したと思います。

リリース版では、qFatalで、即アプリケーションを終了します。
実際に、デバッグ情報には、以下のメッセージが出力されていると思います。

test-Warning
test-Critical
test-Fatal

このようにデバッグ情報をMFCの時と同じように出力できます。
では、C/C++で、一番問題が多いと思われるメモリリークについてはどうでしょうか? Qtでは、どうすればよいでしょう。

メモリリークを出力させてみましょう
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#if !defined(QT_NO_DEBUG) && defined(WIN32)
#include <crtdbg.h>
#endif
 
#include "QApplication"
#include "QPushButton"
 
int main(int argc, char *argv[])
{
#if !defined(QT_NO_DEBUG) && defined(WIN32)
    _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF );
#endif
    QApplication app(argc, argv);
    QPushButton hello("Hello world");
    hello.resize(100, 30);
 
	//	メモリリーク
	int *pint=new int[10];
 
    hello.show();
    return app.exec();
}

一般的にWIN32でメモリリークのチェックを行うときと同じように処理を入れてみました。
結果は、以下のように出力されました。

Detected memory leaks!
Dumping objects -->
{1691} normal block at 0x00AD0320, 40 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

上々の結果です。
Qtをご存知の方は、Qt3で、上記のソースコードでメモリリークが大量に出力されていたのを思い出されていたのではないでしょうか。
(VC++2008とQt4.5.1の組み合わせでは、上記の結果となりました。)
さて、Qtでは、自動的に自身が管理しているオブジェクトを削除するような仕組みを持っています。

例えば、
QWidget に QPushButton を表示させた場合、
QPushButtonは、newで生成され、そのオブジェクトをQWidgetへ配置します。
後は、QWidgetが自身が破棄されるタイミングで、先のQPushButtonも破棄しますので、ユーザは、deleteをしなくて良いようになっています。

この例をサンプルソースコードを使ってあらわしてみましょう。

  • MyWidget.h
    -- メイン画面(ヘッダ)で、終了するボタンを一つだけ持ちます。
  • MyWidget.cpp
    -- メイン画面(ソース)で、終了するボタンを一つだけ持ちます。
  • sample.cpp
    -- メイン画面を表示して、終了するだけです。


[MyWidget.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef _MYWIDGET_H_
#define _MYWIDGET_H_
 
#include "QApplication"
#include "QPushButton"
#include "QVBoxLayout"
 
////////////////////////////////////////
//	メモリーリーク検証のためのデータクラス
class LocalData
{
public:
	LocalData();
	int m_ndata;
};
 
////////////////////////////////////////
//	メモリーリーク検証のための画面クラス
class MyWidget : public QWidget
{
public:
	MyWidget( QWidget *parent=0 ) ;
 
 
	QPushButton *m_pquit;	//	ボタンをnewする
	QVBoxLayout *m_playout;	//	レイアウトをnewする
	QPushButton *m_pquit2;	//	ボタンをnewする
 
	LocalData *m_plocaldata;	//	ローカルデータをnewする
};
#endif
10-15行目は、メモリリーク検証用にQtに依存しないクラスを定義しています。

[MyWidget.cpp]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "MyWidget.h"
 
////////////////////////////////////////
//	メモリーリーク検証のためのデータクラス
LocalData::LocalData ()
{
	m_ndata=99;
}
 
 
////////////////////////////////////////
//	メモリーリーク検証のための画面クラス
MyWidget::MyWidget( QWidget *parent) : QWidget( parent, 0 )
{
	setMinimumSize( 200, 100 );
	setMaximumSize( 400, 400 );
 
	//	ボタンをnewする
	m_pquit   = new QPushButton( "Quit", this );
	//	レイアウトをnewする
	m_playout = new QVBoxLayout;
	m_playout->addWidget(m_pquit);
	setLayout(m_playout);
 
	//	関連のないボタンをnewする
	//	*****このオブジェクトは、メモリーリークと判断される
	m_pquit2   = new QPushButton( "Quit2");
 
	//	ローカルデータをnewする
	//	*****このオブジェクトは、メモリーリークと判断される
	m_plocaldata = new LocalData;
	Q_ASSERT(m_plocaldata->m_ndata==99);
 
	connect( m_pquit, SIGNAL(clicked()), qApp, SLOT(quit()) );
	setWindowTitle(QString ("test dialog"));
}
19行目は、実際にこの画面に貼り付ける"Quit"ボタンを作成(new)しています。
28行目は、この画面に貼り付けない"Quit2"ボタンを作成(new)しています。
32行目は、Qtに依存しませんが、この画面のメンバーとして個別のクラスを生成(new)しています。


[sample.cpp]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#if !defined(QT_NO_DEBUG) && defined(WIN32)
#include <crtdbg.h>
#endif
 
#include "QApplication"
#include "QPushButton"
#include "MyWidget.h"
 
 
int main(int argc, char *argv[])
{
#if !defined(QT_NO_DEBUG) && defined(WIN32)
	_CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF );
#endif
	QApplication app(argc, argv);
	MyWidget hello;
 
	hello.show();
	return app.exec();
}
16,18行目は、画面を表示しているだけです。


では、これをデバッグ版でコンパイルして、デバッグ実行してみると結果は、大量のメモリリーク情報が出力されます。

[MyWidget.cpp]の26-33行目までをコメントアウトして、同様にデバッグ実行してみると結果は、今度はメモリリークが何も 出力されなくなります。

Qtで、きれい?に関連を持たせてあげると、正しく、メモリリークを出力してくれるようです。
Qtで提供されている2,3個のサンプルで、試してみましたが、一応、メモリリークは出力されませんでした。
少しは、このメモリリークのチェック機能も使えるようになったのではないかなと思います。

Qtのメモリリークについては、随分、以前から問題と言うか、チェックの方法について、侃々諤々(かんかんがくがく)、議論があるようです。 Linux系では、無償のメモリチェックツール"Valgrind"が、よく使われていたようです。
Windowsでは、無償のメモリチェックツールはありません。(もしご存知の方がいらっしゃれば、ご一報ください。)
有償の高価?な"Insure++"などが必要とされていました。
確かに、有償パッケージを利用した方が効果的かもしれませんが、以前に比べると、随分、QtもWindowsの環境に馴染んできたのかな という印象を持ちました。(上記のやり方で、どこまで、使えるか不明な点も多いですが・・・。)

この記事で、何かお気づきの点がありましたら、コメントいただければ幸いです。

追記:Toobarやメニューでアイコンを使ったサンプルでは、うまくいかないことがあります。まだ、他にもうまくいかないパターンがあるかもしれませんので、 この中途半端なメモリーリークチェックをご利用の場合は、十分、ご注意ください。
Qtでも、MFCとのMixedを行ったとき、エラーが出るとしています。

※前回のサンプルプログラムに不具合がありましたので、修正しておきました。ダウンロードされた方、すみません。

もっと、Qt関連について詳しく知りたい方は、以下の本なども良いと思います。
Qtに関する日本語の本が少ないですね。「入門書」は、さすがに、このページを読まれるくらいの方は不要だと思います。
やっぱり、本+ネット+試してみる!!の3本柱でやっていく以外にないように思います。


コメント

コメントをどうぞ







  • はてなブックマークへ追加する
  • Facebookでシェアする
  • twitter でつぶやく
  • Google Plusでシェアする
  • Pocketでシェアする
ページトップへ