たのしい工学

プログラミングを学んで、モノをつくりたいひと、効率的に仕事をしたい人のための硬派なブログになりました

【コンピュータサイエンスの独学】関数形式マクロ

   

#define 前処理命令は単純な置き換えであるオブジェクト形式マクロを説明しました。ここではオブジェクト形式マクロに引数を取る機能を合わせた関数形式マクロを説明します。

関数形式マクロ
記述 機能
#define 識別名( arg1 [ , arg2, … ] ) 定義内容
※arg1, arg2 は仮引数識別名 オブジェクト形式マクロ同様、
識別名を定義内容で置換。
更に定義内容の仮引数識別名を
関数形式マクロの実引数で置換。
仮引数識別名はこの定義でのみ有効。

定義した識別名を取り消す場合は#undef 識別名 とします。括弧以降は不要です。
関数形式マクロは関数のプロトタイプ宣言に似ており、仮引数識別名はその#define命令でのみ有効です。
例をご覧ください。


#define MacAdd( x, y ) ( (x) + (y) )
int a = MacAdd( 2, 3 );
int b = MacAdd( a, 5 );

展開後はこうなります。


int a = ( (2) + (3) );
int b = ( (a) + (5) );

MacAdd は二つの引数を取ります。関数とは違い単純な置換なので型はありません。
x の仮引数がマクロ定義の (x) の x に置き換えられる事を意味します。y も同様です。
従って MacAdd( 2, 3 ) の 2 は、定義の x に相当する部分に置換され、 3 は定義の y に相当する部分に置換されます。

単純な置換なのでこんな事も可能です。


#define MacSwap( type, a, b ) 
{                     
type work;    
work = a;     
a    = b;     
b    = work;  
}
double d1 = 1.1;
double d2 = 2.1;
MacSwap( double, d1, d2 ); /* セミコロンに注意 */

展開後は、ここでは空白や改行を見易いように加工してありますがこうなります。


double d1 = 1.1;
double d2 = 2.1;
{
double work;
work = d1;
d1   = d2;
d2   = work;
}; /* セミコロンに注意。空文扱い */

二値を交換するマクロです。 type に型名を指定すると前処理にてその部分が宣言になっている事が分かります。

一見すると関数の様に見えるので関数形式マクロと呼びます。しかし関数とマクロは全く異なるものです。マクロはコンパイルに先立ち行われる単純な置き換えです。一方関数は演算であり副作用を伴います。
単純な置換の怖さを MacSwap マクロを用いて見てみましょう。


int work  = 1;
int work2 = 2;
MacSwap( int, work, work2 );

展開後。注釈や空白等を加工してあります。


int work  = 1;
int work2 = 2;
{
int     work;
work  = work;
work  = work2;
work2 = work;
}

ブロック内の work は全てブロック内で宣言した A の work を指すので二値の交換の目的は達せられません。宣言する変数名は工夫する必要があります。

副作用を考慮しないと置換後に制約違反になる場合があります。


#define MacSecPow( x ) ( (x) * (x) )
int a = 10;
int b = MacSecPow( a++ );

関数形式マクロを使用している式は何の問題も無い様に見えます。展開後。


int a = 10;
int b = ( (a++) * (a++) );

一つのシーケンス・ポイント間で a に対する副作用が二回記述されています。従って制約違反であり結果は未定義、どうなるか分かりません。
関数形式マクロは使い方によっては便利で強力です。しかし僅かな不注意が大きなバグを生み出す可能性もあります。

良く知られた手法を一つ紹介します。
関数形式マクロ定義にブロックが含まれると、その関数形式マクロを使用する時にセミコロンを付ければそれは空文扱いされます。先の MacSwap がその良い例です。 if 文などでブロックを使用せずに次の様に記述すれば文法エラーになります。


if ( z == 1 )
MacSwap( int, a, b ); /* セミコロン */
else
c = a + b;

MacSwap 関数形式マクロはブロックです。展開後次の様になってしまいます。


if ( z == 1 )
{
/* 中略 */
};      /* セミコロンが付くので空文、よって if 文の終了と見なされる */
else    /* if 文は既に終了しているので対応する if が無く、文法違反 */
c = a + b;

もし展開前の記述でブロックを用いていれば回避出来る事なのですが、誰が使うか分からない関数形式マクロ定義をする場合はそうもいきません。またマクロを使用する際セミコロンを付けてはいけないと制約を設けても、どこでセミコロンをつけて良いのか悪いのかと混乱の元です。
そこで関数形式マクロ定義を do ~ while 文で定義します。終了条件の式は 0 とします。条件は常に偽なので do ~ while 内を実行してそのまま繰り返しが終了します。 while の後にセミコロンを付けてはいけません。
MacSwap を改良するとこの様になります。


#define MacSwap( type, a, b ) 
do                    
{                     
type work;    
work = a;     
a    = b;     
b    = work;  
} while ( 0 ) /* セミコロンは付けない */

有名な技ですが美しさには欠けます。 C 言語の構文を逆手に取った誤魔化しに過ぎないからです。とはいえ知っていて損はありません。

今日はここまでです。
ではでは!

 - コンピュータサイエンス