たのしい工学

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

関数形式マクロと、単項演算子、二項演算子、条件分岐

   

関数形式マクロと # 単項演算子

関数形式マクロ専用の単項演算子に # があります。


"#" 単項演算子
記述           機能
#defined 識別子( x ) #x    仮引数 x の文字列を二重引用符で囲み、
文字列リテラルを作り出す。

幾つか特殊な動作をします。
半角空白、水平タブ、改行及びそれに類するものを以降半角空白類と称します。
実引数の先頭部分の半角空白類は削除されます。
実引数の末尾部分の半角空白類も削除されます。
実引数に含まれる複数の半角空白類は一つの半角空白に変換されます。
実引数に二重引用符 " がある場合はその直前に \ を挿入します。
実引数に「 \ で始まる文字定数」が含まれる場合はその直前に\ を挿入します。
変換後が正しい文字列リテラルにならない場合、その結果は未定義です。

\ で始まる文字定数は別項にて説明しますが、エスケープ・シーケンス \n 等とは異なります。

例を見てください。


#define MacPrintf( a, b ) printf( #a, (b) )
MacPrintf( 数値%d\n, i + 4 );

置換後は


printf( "数値%d\n", (i + 4) );

関数形式マクロの第一引数に指定された文字列が a の部分に展開されますが、 # 演算子により文字列リテラルとして先の規則に則った処理が為されます。

"##" 二項演算子

マクロ専用の二項演算子に ## があります。オブジェクト形式マクロでも関数形式マクロでも使用可能です。以後は関数形式マクロについて記述しますがオブジェクト形式マクロでも機能は同じです。


""##"" 二項演算子
記述  機能
# defined 識別子( x, y ) x ## y    仮引数 x の字句と仮引数 y の字句とを連結して一つの字句とする

字句、即ちトークン同士を繋ぎ合わせる為の演算子なのでトークン連結演算子等と呼ばれる事もあります。

例をご覧ください。


#define MacObj( s, n ) ( s ## n )
const int * str1 = "abc";
const int * str2 = "xyz";
printf( "%s\n", MacObj( str, 1 ) );
printf( "%s\n", MacObj( str, 2 ) );

展開後はこうなります。


const int * str1 = "abc";
const int * str2 = "xyz";
printf( "%s\n", ( str1 ) );
printf( "%s\n", ( str2 ) );

MacObj( str, 1 ) とする事で、仮引数の s が str に、 n が 1 になり、これらを連結して str1 が生成されます。

尚 ## 演算子の項の評価順序はコンパイラーによって異なります。又 # 演算子と ## 演算子の評価順序も決められていません。

必要となった時に ## 演算子の有り難味が分かる事でしょう。それまではこの演算子の存在を知っているだけで十分です。

条件判断

前処理命令の条件判断として #ifdef と #ifndef は説明済みです。これらは識別子の判断だけでしたが、定数式の判断を行う命令も用意されています。


条件判断命令
記述  機能
#if 定数式
/* A */
#else
/* B */
#endif  定数式が真 ( 0 以外 ) なら A を、
偽 ( 0 ) なら B をコンパイル対象とする。
#else 及び /* B */ は省略可。
#if 定数式1
/* C */
#elif 定数式2
/* D */
#else
/* E */
#endif  定数式1を評価し、真なら C を対象とする。
定数式1が偽なら、
定数式2を評価し、真なら D を対象とし、
定数式2が偽なら、 E を対象とする。
対象とはコンパイル対象の意。

定数式は整数型の必要があります。
定数式の型は long 又は unsigned long として扱われます。型変換は出来ません。
定数式の評価前にその式の中のマクロ定義が置換されます。ただし単項演算子 defined の対象項は除きます。
この置換によって defined 演算子が出現してはいけません。その場合の動作は未定義です。
文字定数 'a' といった物も使用可能です。ただ文字定数に負が含まれるかどうかはコンパイラーによって決められています。
定数式に列挙定数は使用出来ません。 sizeof 演算子も使用出来ません。
「 #ifdef 識別名」は「 #if defined 識別名」と同じです。
また「 #ifndef 識別名」は「 #if !defined 識別名」と同じです。

この命令はプログラムの版数によってコンパイル条件を変える時等に用いる事もあります。


#define MacPrgVer 45
/* 中略 */
#if MacPrgVer >= 100
/* MacPrgVer >= 100 の場合 */
#elif MacPrgVer >= 30
/* 100 > MacPrgVer >= 30 の場合 */
#else
/* MacPrgVer < 30 の場合 */
#endif

有名な技法を紹介します。
注釈を含む行をコンパイル対象から除外する場合次の様に記述する事で可能です。


#if 0
/* 注釈 */
a++;
#endif

定数式が 0 即ち偽なので #if と #endif の間は全てコンパイル対象から外されます。注釈は入れ子に出来ない為この手法が有効です。
但し次の場合おかしな事になります。


#if 0
/* 注釈を書いている途中
#endif

理由は前処理の段階にあります。

"#"から始まる各種前処理命令は前処理の第四段階で処理されます。一方注釈はそれよりも早い第三段階です。その為コンパイル対象から外されるよりも前に注釈処理は行われます。注釈が始まっているので注釈が終了するまで注釈と見なされ、その結果は説明するまでもありません。そもそも記述を中断して注釈化するなど行儀が良いとはいえません。

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