第19章 組み込みマクロ

 マクロは #define ディレクティブで定義するものですが、中には自動的に定義されているマクロもあります。今回はそのマクロと、使い方について話したいと思います。


 それでは、今回の要点です。


 では、いってみましょう。


 「ある値の評価を行い、その結果がおかしかったときにはエラーメッセージを出す」というのは、デバッグではよく使われます。なので、これを関数にすると便利そうです。

プログラム1プログラム2
// Debug1.h
#ifndef __DEBUG1_H__INCLUDED__
#define __DEBUG1_H__INCLUDED__

#ifdef _DEBUG
// デバッグ時

// ログファイルを開く
void DebugStart();
#define DEBUG_START()  DebugStart()

// 値の確認
void DebugAssert(bool b);
#define ASSERT(b)      DebugAssert((b) != 0)

// ログファイルを閉じる
void DebugEnd();
#define DEBUG_END()    DebugEnd()

#else  #ifdef _DEBUG
// リリース時

#define DEBUG_START()
#define ASSERT(b)
#define DEBUG_END()

#endif  #ifdef _DEBUG

#endif  #ifndef __DEBUG1_H__INCLUDED__
// Debug1.cpp
// デバッグ時のみコンパイルするファイルです
#ifdef _DEBUG

#include <stdio.h>

static FILE* gs_pfLog = NULL;

// ログファイルを開く
void DebugStart()
{
    gs_pfLog = fopen("ErrorLog.txt", "a");
    if(gs_pfLog == NULL)
    {
        printf("エラーログファイルが開けません!");
        exit(1);
    }

    fprintf(gs_pfLog, "ログを開始します\n");
}

// 値の確認
void DebugAssert(bool b)
{
    if(b)
        return;
    if(pfLog != NULL)
        fprintf(gs_pfLog, "値が異常です!\n");
}

// ログファイルを閉じる
void DebugEnd()
{
    fprintf(gs_pfLog, "ログを終了します\n\n");

    if(pfLog != NULL)
        fclose(gs_pfLog);
}

#endif  // #ifdef _DEBUG
プログラム3
#include <iostream.h>
#include "Debug1.h"

int Average(const int pnValues[], const int nData)
{
    ASSERT(pnValues != NULL && nData > 0);
    int i;
    int nSum = 0;

    for(i = 0; i < nData; i++)
        nSum += pnValues[i];
    return nSum / nData;
}

int main()
{
    DEBUG_START();

    int anTest[] = {
        41, 100, 87, 43, 56, 78, 12, 98, 56, 67,
    };

    cout << "平均点は " << Average(anTest, -1) << " 点です。" << endl;

    DEBUG_END();
}

 Average 関数の初めで ASSERT マクロを使っています。このマクロはデバッグ時には DebugAssert 関数を呼び、リリース時には消えてしまいます。

 で、DebugAssert 関数では、条件式が偽の時にエラーメッセージを ErrorLog.txt に出力するようにしています。つまり、ASSERT マクロには常に真の条件式を書いておくようにします。

 これで、上のように Average の第2引数に -1 などを渡すと、エラーメッセージが出力されるわけです。


 しかし、この関数には致命的な欠点があります。それは、どの ASSERT でエラーが起こったのかが分からないことです。これは致命的です。

 こういうときのためにかは知りませんが、ファイル名と行番号に置き換わるマクロが存在します。それが __FILE____LINE__ です。

 これを使えば、次のようにして欠点を解決できます。

// 値の確認
void DebugAssert(bool b, const char* pszFile, int nLine);
#define ASSERT(b) DebugAssert((b) != 0, __FILE__, __LINE__)
// 値の確認
void DebugAssert(bool b, const char* pszFile, int nLine)
{
    if(b)
        return;
    if(pfLog != NULL)
        fprintf(gs_pfLog, "%s (%d)\n値が異常です!\n", pszFile, nLine);
}

 「あれ? __FILE__ はいつも "Debug1.h" になるんじゃないの」と思われるかもしれませんが、そうはなりません。先ず ASSERT マクロが展開され、その後に __FILE__ マクロが展開されるので、ASSERT マクロの展開先のファイル名、行番号に展開されます


 __FILE__ や __LINE__ などは、どこでも定義されていません。__FILE__ はともかく、__LINE__ を #define を使って定義することなどできるわけがありません。このような、定義しなくても勝手に定義されるようなマクロのことを組み込みマクロと呼びます。

 ANSI Cは、6種類の組み込みマクロを定めています。

マクロ名 どう展開されるか
__FILE__ ファイル名のリテラル文字列。
__LINE__ 行番号を示す10進の数字。
__DATE__ 現在のソースファイルをコンパイルしたときの日付。月、日、年の順のリテラル文字列になります。
__TIME__ 現在のソースファイルをコンパイルしたときの時刻。時:分:秒の形式のリテラル文字列になります。
__TIMESTAMP__ 現在のソースファイルの保存日時。曜日、月、日、年、時、分、秒の順のリテラル文字列になります。
__STDC__ ANSI Cに準拠しているかどうかを示します。準拠しているときは1と定義され、していないときは定義されません。

 このほかにも、ツール毎に組み込みマクロが用意されていることがあります。他のツールでは定義されていないことを頭に入れながらであれば、積極的に使ってもさほど問題はないでしょう。


 今回のデバッグ関数はまだ発展の余地があります。エラー時刻を保存したり、値を表示したり、もっと使い勝手をよくしたり...。

 そして、VC++では crtdbg.h に色々なデバッグ関数が用意されています。他のツールでも似たようなものが用意されていることが多いと思います。

 ここでは、そのものズバリな _ASSERT マクロも用意されています。そこで処理を止めて、そこからデバッグを行うことができるので、使い勝手はいいと思います。

 これらのことについても、自分で調べて、ものにしておくと便利だと思います。


 それでは、今回の要点です。


 次回は自分の好きな、とはいえほとんど使う機会のない命令についてのお話です。使う機会があると思わず小躍りしてしまうような、そんな楽しくてマイナーな奴です。


第18章 if...2 | 第20章 トークンを結合せよ

Last update was done on 2000.8.18

この講座の著作権はロベールが保有しています