Another introduction to programming language C 06: プリプロセス

C言語はコンパイル前にプリプロセスされる.プリプロセスのほとんどはマクロの処理だ.マクロとは,本来プログラムを生成するプログラムのことだが,C言語のマクロはおそらく現在使われているマクロのなかで最も貧弱かつ脆弱なものだ.

率直に言えば,C言語のマクロは使わない方がいい.マクロが必要になったら,C++言語への乗り換えを検討する時期である.

マクロのうち,定数マクロは比較的害が少ない.定数マクロとは,定数に別名を与えるものである.例えば次の通り.

#define YES 1
#define NO 0

これ以降,プリプロセッサはコンパイルに先立って,プログラム中のYESを1に,NOを0に置き換える.この程度の使用ならば,許容範囲である.(C++には定数変数と列挙子というよりよい代替案がある.)

次のマクロは,よく使われるが,お勧めしない.

#define MAX(x, y) ((x) > (y) ? (x) : (y))

これは変数xと変数yの大きい方を返す関数のように見えるマクロである.同じことをする関数

int max(int x, int y) {
  if (x > y) {
    return x;
  }
  else {
    return y;
  }
}

のほうが,はるかに安全で,かつ読みやすい.(int型以外も扱いたいのなら,C++言語に移行すべきだ.C++ならば次のように書ける.

// C++
template <typename T> T max(T x, T y) {
  if (x > y) {
    return x;
  }
  else {
    return y;
  }
}

これは任意の型に対する関数をコンパイル時に生成する.)

プリプロセッサのもう一つの用途は,条件コンパイルである.ただし,多くの場合は次のような場当たり的な使われ方をする.

#if 1
/* 新しいコード */
...
#else
/* 古いコード */
...
#endif

これは,古いコードを新しいコードに書き換えるときに,古いコードを消したくないときによく使われるテクニックである.プリプロセッサは #if 1 ... #else までをコンパイラに引き渡し,#else ... #endif の間を捨てる.もしうまくいかなかったら,#if 1 を #if 0 に置き換えると過去の栄光を取り戻せる.しかし,ものの10分もすると,あなたの脳は過去の栄光を忘れる.

この場当たり的な条件コンパイルに関するよい代替案はなかなか見つからない.要するにソースコードの変更へのundoが出来ればよいのだから,エディタや外部ツール(バージョンコントロールシステムとして知られる)の機能を借りるのが一番よい.また,新しいコードに置き換えられる程度のかたまりのブロックなのだから,さっさと関数にしてしまったほうが良いのかもしれない.そうすれば,新しい関数にはnew_funcという名前をつけて,オリジナルの関数funcと置き換えてみることが出来る.前者には新しいツールを覚えなければならないというハードルがあるし,後者には変数をすべて引数として渡さないといけないというハードルがあるが,使い捨てのプログラムでなければ,検討する価値はある.

#includeはこのレクチャーで最初に見たプリプロセッサ指令である.もし,#inculdeで同じファイルを2回インクルードしたらどうなるだろう.

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

宣言は2回してもよいが,定義は2回してはならないので,困ったことがおこる可能性はある.普段は気にする必要はないかもしれないが,多重インクルードを防ぐ巧妙なテクニックがある.stdio.hにはすでにそのテクニックが施されている.それは

/* stdio.h */
#ifndef __STDIO_H
#define __STDIO_H
...
#endif

というふうに,ヘッダファイルの中身を #ifndef ... #endif で囲っておくのである.#ifndef A は「もしAというマクロが定義されていなかったら」という意味である.(「もしAというマクロがていぎされていたら」という意味の #ifdef A もある.)

stdio.hの中身はこう読める.「もし__STDIO_Hというマクロが定義されていなかったら,__STDIO_Hというマクロを定義する.(本文.)条件コンパイル終わり.」もし誰かが__STDIO_Hを一足先に定義していたら,このテクニックはうまくいかない.C言語では_で始まるマクロ,関数名をプログラマが勝手に使わないことを推奨している.

このようなテクニックをインクルードガードという.(Objective-Cではインクルードガードは必要ない.#includeのかわりに#importを使えば,プリプロセッサが自動的に多重インクルードを避ける.)

C言語のプリプロセッサは使いこなす価値がない.得られる見返りはわずかである.どうしてもC言語を使わなくてはならない状況で,高度なマクロが使いたければm4の検討をすすめる.
Comments