先の define 文には別の機能がある。それは、マクロ機能である。 マクロ機能を用いると、あたかも関数のように使いつつ、関数の ような引数の型に対する厳格なチェックを避ける事ができる。
マクロ機能は次のようにして使う。
#define マクロ名(引数) 引数を含む文字列 |
但し、単なる置き換えであるために、いわゆる副作用と呼ばれる 問題が生じる事があり、注意が必要である。例えば、C言語には 二乗のための演算記号がないので、それを導入したいと考えた 場合、関数で実現しようとすると、整数・実数それぞれの場合に ついて関数を用意しなければならなくなる。マクロならば、次の 例のように整数・実数の区別なく導入が可能となる。
/* 副作用がある例 */ #define sq(x) x*x |
しかし、上のようにした場合、マクロは単なる置き換えである
ために、sq(x)
を x*x
に置き換える訳である
から、sq(3+2)
のように使うと、3+2*3+2
と
置き換えてしまい、意図したようには動かない。従って、これを
避けるには、次のように括弧を適切に用いなければならない。
/* 副作用を考慮したが、実はまだダメな例 */ #define sq(x) (x)*(x) |
こうすることで、先の sq(3+2)
は (3+2)*(3+2)
と
展開され、意図したように動く。しかし、実は、これでもまだダメ
な場合がある。例えば、5/sq(2)
を考えよう。本来これは、
5/4
のつもりで書いたのであるが、マクロ展開の結果は
5/(2)*(2)
となる。つまり、結果は予期しない 4 となって
しまうのである。これを避け、正しくは次のようにマクロを定義
しなければならない。
/* 副作用を完全に考慮した例 */ #define sq(x) ((x)*(x)) |
このように、一般に個々の引数を ( )
で括り、更に
展開文字列全体も ( )
で括る必要がある。
但し、ここまで注意深く行っても、避ける事の出来ない問題もある。
/* 2剰マクロの誤用例 */ sq(++x) |
この場合、sq(++x)
は (++x)*(++x)
に展開される
ので、意図したような値にならないのである。これに関しては、
全く回避不可能という訳ではないが、一般にマクロにはこうした
問題が生じる事は知っておかなければならない。
とは言え、こうした点に注意するならばマクロ定義は便利なものであり、
実際に、前回に学習した getchar()
や putchar()
は stdio.h
の中でマクロ定義を使って実現されている。
/* stdio.h の一部 */ #define getchar() getc(stdin) #define putchar(x) putc(x, stdout) |
ここで、標準入力や、標準出力は stdio.h の中でこうしたマクロを用いて、
それぞれ stdin
, stdout
として定義されている。
このようにマクロ定義はうまく使うとソースコードを短くしたり、 可読性を高める事が出来ますが、 マクロ定義では定義中の行末までがその対象になるのでプログラム を置き換えるような場合に改行を含めてはいけない点に注意して ください。
なお、マクロに空の実体を定義することもできる。
#define noaction() |
但し、この場合には引数はないので、もし引数をとるマクロを空にしたい 場合にはやはり引数を書く必要がある。