簡易的な関数の実現
#define疑似命令の高度な機能
第1節で紹介した#define疑似命令は定数を宣言する疑似命令ですが、
実は、それ以上に高度な機能を持ち合わせています。
#define疑似命令による定数は、単なる置き換えによって実現されていますが、
これを利用すると特殊な処理を行わせることも可能です。
たとえば、変数の中身を画面に表示するにはprintf文を次のように使用します。
printf("temp = %d\n", temp);
しかし、変数tempの値をあちこちで表示する必要がある場合に、
この文を毎回打ち込むのが面倒であれば、#define疑似命令で置き換えることができます。
#include <stdio.h>
#define PRINT_TEMP printf("temp = %d\n", temp)
int main(void)
{
int temp = 100;
PRINT_TEMP;
return 0;
}
このプログラムの実行結果は、次の通りになります。
このプログラムのトリックは、#define疑似命令の定義内容にあります。
#define疑似命令の機能は単なる置き換えで、数値でなくとも何でも置き換えできます。
ここでは、
PRINT_TEMP が変数を表示するprintf文にそのまま置き換わっています。
つまり、プログラム的にはその部分にprintf文を書いているのとまったく同じです。
これは、あらゆるプログラムを強引にまとめることができる、非常に強力な機能です。
しかし、上記のテクニックを乱用すると、プログラムが省略表現だらけになってしまって、
最初にプログラムを書いた人にしか読めなくなってしまうので、十分に注意して使用する必要があります。
マクロという簡易関数
前項で見た通り#define疑似命令は非常に強力ですが、
実は、#define疑似命令にはさらに強力な機能が備わっています。
#define疑似命令で
簡単な関数を作ってしまうことが可能です。
#define疑似命令では、名前の後に()で文字を指定すると、
以後の置き換える内容で同じアルファベットの部分を置き換えることができます。
たとえば、次の#define疑似命令は、指定されたint型の変数を画面に表示します。
#define PRINTM(X) printf("%d\n", X)
次のプログラムは、先ほどの#define疑似命令を使う例です。
#include <stdio.h>
#define PRINTM(X) printf("%d\n", X)
int main(void)
{
int a1 = 100, a2 = 50;
PRINTM(a1);
PRINTM(a2);
return 0;
}
このプログラムの実行結果は、次の通りになります。
ここの#define疑似命令では、名前の後の()の中でXが指定されています。
これによって、以後の置き換え文の中のXは、
#define疑似命令を使用した時の()内の記号に置き換えられます。
先ほどの例では、Xがa1やa2に置き換わった内容になったのです。
この機能や使い方を見る限り、これはまるで関数のようです。
実際、この機能は簡易的な関数の変わりに使われており、
マクロと呼ばれています。
【マクロ】
#define疑似命令による置き換えで式などを簡単に表現すること。
マクロの使い方は普通の関数とまったく同じですが、仕組みは大きく異なります。
関数の場合、その実態は1か所にあり、必要な時に呼び出されて使われます。
しかし、マクロでは、使用している場所のプログラムそれ自体が置き換わり、
呼び出しなどの作業が必要ないため、若干高速になります。
しかし、マクロを使う場所すべてが置き換わるので、あまり巨大なマクロを作ると、
その為にプログラムのサイズが極端に大きくなることもあります。
その為、一般的には、マクロは、
決まり切った数式などに利用されます。
たとえば、次のマクロは台形の面積を求めるマクロです。
#include <stdio.h>
#define GET_TRAPEZOID_AREA(A, B, H) (A + B) * H / 2
int main(void)
{
int up, down, h, s;
printf("上底、下底、高さ:");
scanf("%d,%d,%d", &up, &down, &h);
s = GET_TRAPEZOID_AREA(up, down, h);
printf("面積:%d\n", s);
return 0;
}
このプログラムの実行結果は次の通りになります。
この様にすれば、決まり切った数式を何度も入力する必要がなくなります。
副作用の恐怖
#define疑似命令によるマクロは手軽で便利なのですが、
使い方を間違えると思わぬ現象に遭遇することがあります。
たとえば、前回の台形の面積を求めるプログラムに置いて、
なんらかの事情で、高さを常に+3しなければならない場合を考えてみます。
次のプログラムは、そのように変更してみた例です。
#include <stdio.h>
#define GET_TRAPEZOID_AREA(A, B, H) (A + B) * H / 2
int main(void)
{
int up, down, h, s;
printf("上底、下底、高さ:");
scanf("%d,%d,%d", &up, &down, &h);
s = GET_TRAPEZOID_AREA(up, down, h + 3);
printf("面積:%d\n", s);
return 0;
}
このプログラムの実行結果は次の通りになります。
高さは+3されているので、結果は前回と同じになるはずなのですが、
なぜか、答えは76と表示されてしまっています。
これは、#define疑似命令は、単なる置き換え命令でしかないため、
GET_TRAPEZOID_AREA(up,down,h + 3) を (A + B) * H / 2 と置き換えると、
(up + down) * h + 3 / 2 と言う式になってしまいます。
これでは、高さに3が加わるのではなくなってしまい、計算がおかしくなります。
この様に、置き換えで予期しない計算結果になることを
マクロの副作用と呼びます。
これを解決する方法は2つあります。1つは、
呼び出し時にかっこをつけることです。
GET_TRAPEZOID_AREA(up, down, (h + 3));
と、かっこをつけておけば先に高さに3が加わるので、正常に計算できます。
もう1つの方法は、
マクロの方にかっこをつけておく方法です。
マクロで使われている置き換え部分すべてにかっこをつけ、さらにマクロ全体にもつけます。
#define GET_TRAPEZOID_AREA(A, B, H) (((A) + (B)) * (H) / 2)
この様にすれば、すべての数値にかっこが付いているので大丈夫です。
しかし、気をつけて使用するのは面倒ですし、うっかり忘れてしまうかもしれません。
その為、マクロはあまり多用しない方が良いとされています。
#define疑似命令は定数の宣言にのみ使用して、
数式などの計算にはできる限り関数を使用する方が良いでしょう。