C におけるデバッグ出力のジレンマ

「開発中にデバッグログを出力したい。でもリリース時には出力したくない」、というような要求はかなりの頻度である。

こんな感じでやることになるだろう(デバッグとリリースを切り分ける #ifdef は省略)。

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

#define debug1(fmt, ...) printf("%s(%d):" fmt, __func__, __LINE__, __VA_ARGS__)
#define debug2(arg) do { printf("%s(%d):", __func__, __LINE__), printf arg; } while (0)

int debug3(const char *fmt, ...) {
    int rc;
    va_list va;
    va_start(va, fmt);
    rc = vprintf(fmt, va);
    va_end(va);
    return rc;
}

int main(int argc, char *argv[]) {
    debug1("debug1:%s\n", "arg");
    debug2(("debug2:%s\n", "arg"));
    debug3("debug3:%s\n", "arg");
    debug1("debug1\n", NULL);
    debug2(("debug2\n"));
    debug3("debug3\n");
    return 0;
}

処理系が C99 をサポートしているなら、よく使われるのは debug1 だろうか。きわめて簡素かつ自然に書けるものの、可変長引数が必要ない場合でも書かなければならないという欠点がある*1。しかも上記の例だと、-Wall などで printf に対する不要なパラメータが存在すると警告が出てしまう。

処理系が C99 をサポートしていないなら、debug2 だろうか。これは引数のカッコが二重になってしまうという見た目と書きにくさの欠点がある。ただし、警告等は一切出ない。

万能なのは debug3 だが、これは関数を定義してしまい、リリース時にも実体が存在してしまうという問題に加え、__func__、__LINE__ 等のデバッグ出力に必須ともいえるマクロが使用できなくなってしまうのがつらい*2

僕自身は、debug1 の場合もあれば debug2 の場合もある。なお C++ の場合だとまた状況が変わってくる。

2012/08/14 追記:

コメント欄で以下の方法を教えてもらった。

#define debug4(...) {printf("%s(%d):", __func__, __LINE__); printf(__VA_ARGS__);}

なるほど、これなら可変長文字列が不要な場合に、不要な NULL を渡さなくてもよい。

*1:処理系によっては省略できるものも

*2:glibc なら backtrace を使用して実行時に呼び出し元の関数名を取得することは可能ではあるが、さらに処理系にも依存してしまうという問題が増えてしまう