検索
連載

プリプロセッサでプログラムの質を向上させよう目指せ! Cプログラマ(16)(3/4 ページ)

プログラミング言語Cの強力な機能の1つに、「プリプロセッサ」があります。正しく使えば、間違いが少なくて、意味も伝わりやすいプログラムを、より簡単に書くことができます。プリプロセッサを使いこなして、プログラムの質をぐっと向上させましょう。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

「#」演算子と「##」演算子

 マクロ定義では、「#」演算子と「##」演算子が使えます。

 引数を持つマクロ(関数型マクロ)では記述した引数がそのまま評価されますが、仮引数の前に「#」演算子を付けると、実引数がそのまま文字列になります。

// 通常のマクロの例
#define MACRO_A(a) a
int x = MACRO_A(1 + 2); // int x = 1 + 2;
// #演算子を使ったマクロの例
#define MACRO_B(b) # b
char * y = MACRO_B(1 + 2); // char * y = "1 + 2";

 同じく関数型マクロで、仮引数の前または後に「##」を付けると、その前後に書かれたコードと連結されて、コードの一部になります。

// ## 演算子を使ったマクロの例
#define MACRO_C(a) value ## a
int value1;
// ...
x = MACRO_C(1); // x = value1;

 このマクロ「MACRO_C」では、「value」と、仮引数「a」に対応する実引数「1」が連結され、「value1」というコードが生成されます。

 前述のコードですとほとんど意味がありませんが、さまざまな応用ができます。次の例を考えてみましょう。

typedef struct i_vec2_t {
    int x, y;
} i_vec2;

 int型のメンバー「x」と「y」を持つ2次元座標の構造体を定義しています。これは、「##」演算子を使って次のように書けます。

#define DEF_VEC2(type, name)      \
typedef struct name ## _vec2_t {  \
    type x, y;                    \
} name ## _vec2
DEF_VEC2(int, i);
i_vec2 point = { 1, 2 };

 マクロ定義の行の最後にある円マーク(あるいはバックスラッシュ)は、行が続いていることを表します。マクロは必ず1行で表す必要があるため、このように複数の行にわたって書くときは、行の最後に円マークを付けるのです。

 このマクロを使うことで、int以外の型のvec2構造体を簡単に用意できます。ここでint64_tという型が出てきていますが、これはstdint.hで定義されている64ビットの整数を表す型です。

DEF_VEC2(unsigned long int, uli); // uli_vec2
DEF_VEC2(int64_t, i64); // i64_vec2
DEF_VEC2(double, ); // _vec2

 最後の例のように実引数を省略した場合、仮引数の位置には何もなかったものとして処理されます。

条件付き取り込み

 プリプロセッサの機能を使って、プログラムの一部を有効にしたり無効にしたりすることができます。Cの条件文(if)と同じようにプリプロセッサ用にも「#if」が用意されており、ここで指定された条件を満たしたとき(値が0ではないとき)に、「#endif」までを有効にします。

int x = 0;
#if 1
x = 1;
#endif

 このプログラムは「#if」に指定した条件式「1」が有効なため、xに1が代入されます。このプログラムの 「#if 1」の「1」を「0」にすると、「x = 1;」の行がないものとみなされ、xの値は0のままになります。「#if」に指定された条件が満たされないと、「#endif」までの行(「#else」がある場合はそこまでの行)がプリプロセッサの出力として出てこないため、コンパイルするときにはコード上から消えてしまうからです。

 「#if」の条件式には、Cの式がそのまま書けます。従って比較演算子(「==」や「>」など)、論理演算子(「&&」や「||」)を使って式を書きます。ただし、プリプロセッサはコンパイラを呼び出したときに実行されますので、コンパイル時点で値が定まる式でなければなりません。

 また、「#else」を使うと、そこから「#endif」までのコードは、「#if」の条件を満たさないときに有効になります。

#if 条件
// 条件を満たしているときに有効
#else
// 条件を満たしていないときに有効
#endif

 さらに複数の条件をつなげるときには「#elif」を使います。

#if 条件1
// 条件1を満たしているときに有効
#elif 条件2
// 条件2を満たしているときに有効
#else
// いずれの条件も満たしていないときに有効
#endif

 条件式にはマクロを記述することもできます。そのマクロは置換されてから評価されます。

definedを使った条件付き取り込み

 指定したマクロが定義されているかどうかを判定するために、「defined」というプリプロセッサの条件式だけで使える演算子があります。

#define ABC
#if defined(ABC)
// この中は有効。
#endif

 「defined」演算子は定義されているかどうかを調べるだけですので、値は関係ありません。また、「defined」のカッコも省略可能です。

#if defined ABC

 このほか、「#if defined」の代わりに「#ifdef」が、「#if ! defined」の代わりに「#ifndef」が使えます。

#ifdef ABC
// #if defined(ABC) と同じ
// マクロ ABC が定義されていればこの中は有効
#endif
#ifndef ABC
// #if ! defined(ABC) と同じ。
// マクロ ABC が定義されていなければこの中は有効。
#endif

 よく使われる例として、ヘッダの2重取り込みの防止があります。同じ識別子のマクロを2つ同時に定義できませんので、1つのソースファイルで同じヘッダを2回以上インクルードしてしまうと、大抵の場合コンパイルエラーになります。これを避けるには条件付き取り込みを利用して、ヘッダの先頭で次のように記述します。

#ifndef HEADER_1_
#define HEADER_1_
// ヘッダの中身
#endif

 マクロの名前には、ヘッダ名を利用するなどして、個別の名前を付けるようにします。

 1回目のインクルードのときは「HEADER_1_」マクロは定義されていませんので、そのヘッダの中身が有効になります。併せて「#define HEADER_1_」も有効になるため、「HEADER_1_」マクロが定義されます。2回目以降は「HEADER_1_」が定義されていますので、ヘッダの中身は無効になります。

 このほか、条件付きマクロを使って記述を分ける方法は次のような場所でも使われています。

  • コンパイラの種類やバージョンごとに異なるコードを記述する
  • 実行コードの対象となる環境ごとに異なるコードを記述する
  • デバッグ用のコードをデバッグ時のみ有効にする
  • assertマクロはNDEBUGを定義していると何も処理しない(本連載第15回を参照

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る