printfデバッグを極める

研究の実装に役立ちそうな、C++プログラミングのちょっとしたテクニックご紹介します。

一番シンプルなデバッグの方法といえばprintf関数でコンソールにメッセージを出力する方法だと思います。

ところが、あちこちにデバッグ用のprintfを書いておくと、だんだん出力される情報が多くなってきて困ったりします。そのうち過去のコードを探って不要になったprintfをコメントアウトして回る作業をする羽目になるでしょう。

せっかく書いたprintf文をいちいち消して回るのも面倒です。もしかしたらまた必要になるかもしれません。
消したり書いたりしないで済むために、出力する情報を選別できたら便利だと思いませんか?

今回はコンソールに出力するデバッグメッセージを"冗長性"と"チャンネル"の2種類の分類で管理する方法を説明します。といってもそれほど複雑なことをする必要はなく、次のようなコードを用意します。

//Channel Mask
#define CHANNEL_ANY          0xFFFFFFFF
#define CHANNEL_FILE_LOAD       0x00000001
#define CHANNEL_INVALID_VALUE     0x00000002

int g_verbosity = 3;
DBG_CHANNEL g_channel = CHANNEL_ANY;

void printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, ... )
{
  if( g_verbosity >= verbosity ){
    if( (g_channel & channel) != 0 ){
      const int MAX_CHARS = 1023;

      va_list argList;
      va_start( argList, format );
      static char s_buffer[MAX_CHARS + 1];
      vsnprintf( s_buffer, MAX_CHARS, format, argList );
      va_end( argList );

      printf( s_buffer );
    }
  }
}

例えばこんな風につかいます。

//...何らかの処理...
printDebugMsg( 3, CHANNEL_FILE_LOAD, "Load File %s\n", fileName );

一番目と二番目の引数に管理用の情報を指定する以外はおなじみのprintfと同じように使えます。

一番目の引数に入れた値は 冗長性(verbosity)を表す整数です。数字が小さいほど重要な情報ということになります。
これを指定しておくことで、「冗長性3以下のメッセージのみ表示する」といったように、出力するメッセージを選別できます。

二番目の引数に指定したのはチャンネルです。コードの一番上でdefineしてますが、要はビットマスクです。
チャンネルの種類を追加する時は
...
#define CHANNEL_A  0x00000004
#define CHANNEL_B  0x00000008
...
のように2のべき数で指定しましょう。
これを指定しておくことで、「ファイル入出力に関するメッセージ(CHANNEL_FILE_LOAD)だけ表示する」といったフィルタリングができます。

この関数をprintfの代わりに使っておくことで、後から表示するメッセージを管理できます。
上のコードでは、グローバル変数のg_verbosityとg_channelを書き換えれば、
「任意の冗長性以下の任意のチャンネルのメッセージ」だけ表示させることができます。
説明のためにグローバルに置いてありますが、ちゃんとクラス化したものを記事の最後に掲載するので、もしよければ好きに使ってください。あるいはコードレビューも歓迎です。

ところで、printDebugMsg関数の引数のところに見慣れない書き方があると思います。

void printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, <span class="deco" style="color:#FF0000;">...</span> )

最後の"..."ってなんでしょう?

これは、知ってる人は知ってると思いますが、任意数の引数を表します。
任意数の引数をどうやって扱うかというと
まず、va_listという型の変数を用意します。(typedefされてる void* です)
va_startマクロで引数の始まり、つまり可変長引数の直前の引数を指定します。
vsnprintf関数を使って可変長引数を文字配列に書き込むことができます。
最後にva_endマクロで締めくくっておきましょう。お約束ってやつです。
もちろん文字配列に書き込む以外に、可変長引数を順番に取り出して使う方法もあります。詳しくはグーグル先生に。

以下にデバッグメッセージ用クラスを掲載します。

ヘッダファイル

#ifndef INCLUDED_DEBUG_PRINTER_H
#define INCLUDED_DEBUG_PRINTER_H

//Channel Mask
#define CHANNEL_ANY						0xFFFFFFFF
#define CHANNEL_FILE_LOAD				0x00000001
#define CHANNEL_INVALID_VALUE			0x00000002

class DebugPrinter
{
typedef unsigned int DBG_CHANNEL;

private:
	static int d_verbosity;
	static DBG_CHANNEL d_channel;

	DebugPrinter(){}
	DebugPrinter( const DebugPrinter& rhs );
	DebugPrinter& operator=( const DebugPrinter& rhs );

public:

	static void printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, ... );
	static void setVerbosity( int verbosity ){ d_verbosity = verbosity; }
	static void setChannel( DBG_CHANNEL channel ){ d_channel = channel; }
};

#endif


ソースファイル

#include "DebugPrinter.h"
#include  <stdio.h>
#include <stdarg.h>

int DebugPrinter::d_verbosity = 3;
DebugPrinter::DBG_CHANNEL DebugPrinter::d_channel = CHANNEL_ANY;

void DebugPrinter::printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, ... )
{
	if( d_verbosity >= verbosity ){
		if( (d_channel & channel) != 0 ){
			const int MAX_CHARS = 1023;

			va_list argList;
			va_start( argList, format );
			static char s_buffer[MAX_CHARS + 1];
			vsnprintf( s_buffer, MAX_CHARS, format, argList );
			va_end( argList );

			printf( s_buffer );
		}
	}
}

TABのインデントが無視されて表示されるようですね。
ちょっと見にくくなってすいません。

(山田)

参考文献 "ゲームエンジンアーキテクチャ", ジェイソン・グレゴリー 著, 大貫 宏美, 田中 幸 訳, 今給黎 隆, 桐山 忍, 鴨島 潤, 湊 和久 監修, ソフトバンク クリエイティブ株式会社.