トップC言語入門 > マクロ
マクロ

C言語のマクロは、主に、実行時間の短い実行ファイルを生成するためのものである。 CPU性能向上により、その意義は薄まっているので、なるべくつかなわい方がいい。 しかし、過去のプログラムでは、しばしば使われているため、理解しておく必要がある。

1.マクロ

よく使う例を下に示す。

#define  MAX_NODENUM    14000               // 最大ノード数
double X[MAX_NODENUM], Y[MAX_NODENUM];      // X, Y座標格納配列

上のプログラムはプリプロセッサが下のように書き変えてから、コンパイルが行われる。

double X[14000], Y[14000];                  // X, Y座標格納配列

配列のサイズをプログラム中で何度か使う場合、修正するとき、1か所書き変えるだけでよい。

また、たとえ1か所でしか使われない場合でも、数値では意味が分からない。 適切な文字で表せばプログラムの意味が分かりやすい。 重要なデータの場合、プログラムの先頭で定義しておけば分かりやすいという利点もある。

上の例では 文字列が一つの数値に置き換えられたが、一つとは限らず、スペース以降の記述全体が置き換えられる。 下の例では XYZ が ((int)(sqrt(v) + 0.5)) に置き換えられる。

#define  XYZ    ((int)(sqrt(v) + 0.5))

最初の例のように、1つの数値に置き換える場合は別であるが、一般には、置き換える記述全体を ( ) でくくっておく。 さもないと、思わぬエラーに見舞われる。

マクロのもう一つの形が下の例に示す関数形式のマクロである。 この場合、個々の引数も ( ) でくくる必要があるため、 全体として、( ) の数が多いため、慣れないと、理解しずらいことが多い。

  #define max(a, b) ((a) > (b) ? (a) : (b))

プログラム中に max(n*n, 100) があると、((n*n) > (100) ? (n*n) : (100)) に置き換えられる。

もし max(++n, 100) と記述した場合、((++n) > (100) ? (++n) : (100)) に置き換えられる。 これは n を int型とした場合、下の関数とは振舞いが異なってくる。 例えば n の値が 200 の場合、上のマクロが返す値は 202 で、n の値は 202 に変わる。 下の関数の場合、戻り値は 201 で、n の値は 201 に変わる。このように、想定していない動きを副作用と呼んでいる。

  int max(int a, int b) { return (a > b ? a : b); }

また、関数本体では上の例では a, b は (a), (b) としなくてもよいが、 マクロの場合は (a)、(b) のように ( ) で括っておかないと、予想外のプログラムに展開される場合がある。

下の分かりやすい例で説明する。

  #define mul(a, b) (a * b)
もし、上のように定義した場合 mul(2+1, 10) は 30 にはならない。 (2+1*10) と展開されるため、12 になる。

以上のこと(特に副作用があること)から、関数形式のマクロは一般には(特に初心者は)使わない方がよいとされている。

Windows標準ライブラリなどでは使われている。 本ホームページに掲載のプログラムでも下の例のように、ときたま関数形式のマクロを使用している。

#define D(n,m)  Dist[Tour[(n)%N]][Tour[(m)%N]]

void TwoOpt(int Tour[], int N) {
    for (int fImproved = 1; fImproved; ) {
        fImproved = 0;
        for (int i = 0; i < N-1; i++) {
            for (int j = i+1; j < N; j++) {
                if (D(i,j) + D(i+1,j+1) < D(i,i+1) + D(j,j+1)) {
                    for (int k = 0; k < (j-i)/2; k++) {
                        Swap(Tour, (i+1+k)%N, (j-k)%N); // 部分経路を逆順に並び替える
                    }
                    fImproved = 1;   // 改善した
                }
            }
        }
    }
}

マクロを使わない場合には、下のように [ ] が多くなり、プログラムが非常に分かりづらい。 関数形式のマクロがどのように展開されるかを十二分に理解している場合に限り、使用してもいいであろう。

void TwoOpt(int Tour[], int N) {
    for (int fImproved = 1; fImproved; ) {
        fImproved = 0;
        for (int i = 0; i < N-1; i++) {
            for (int j = i+1; j < N; j++) {
                if (Dist[Tour[i%N]][Tour[j%N]] + Dist[Tour[(i+1)%N]][Tour[(j+1)%N]]
                    < Dist[Tour[i%N]][Tour[(i+1)%N]] + Dist[Tour[j%N]][Tour[(j+1)%N]]) {
                    for (int k = 0; k < (j-i)/2; k++) {
                        Swap(Tour, (i+1+k)%N, (j-k)%N); // 部分経路を逆順に並び替える
                    }
                    fImproved = 1;   // 改善した
                }
            }
        }
    }
}

この場合、マクロではなく、次のように関数を使ってもよい。

int D(int n, int m) { return Dist[Tour[n%N]][Tour[m%N]]; }

ただし、マクロの方が、実行時間が短い実行ファイル(実行可能形式ファイル)が得られる。

次のように、インライン関数にすれば、関数型マクロと同じように関数呼び出しのオーバヘッドがない コードが生成される。(最適化レベルを上げないとインライン展開されないことがあるようなので、 実行時間を比較するなどにより、効果を確認した方がよい。)

inline static int D(int n, int m) { return Dist[Tour[n%N]][Tour[m%N]]; }

参考文献

[1] マクロ (コンピュータ用語)