Version 6.05
プリプロセッサとマクロ
「ここ最近よく出てくる #include や #define 」
『最初が # で始まる謎なヤツ』
「こういうのを〈プリプロセッサディレクティブ〉って言います」
『??』
「英語で書くと Preprocessor Directive 。 Pre-process は〈事前処理〉
って意味。 Directive は指示する物って意味」
『前は〈プリプロセッサ〉って、〈ディレクティブ〉は省略してなかったん
じゃない?』
「うん、長いからプリプロセッサ、さらに〈マクロ〉って言うことも多いか
な」
『なんか全然違うけど……』
「マクロの話は後半で。ここでは〈プリプロセッサ〉で統一します」
『で、〈事前処理〉って何の前処理?』
「コンパイルの。つまりプリプロセッサディレクティブは、コンパイル前に
色々と処理をするためのものってこと」
『コンパイル前、ねぇ』
「分かりにくいかもね。ひとつ例を挙げると」
const int DEF_FLAG_OK = 1;
『定数作るのだね』
「これと」
#define DEF_FLAG_OK 1 // 定数。
『あ、これも定数作るのだ。でもこれはプリプロセッサのだね』
「機能的にはこのふたつは似てるけど、仕組みがまったく違うんです。まず
const int の方。これは普通の int 型の、中身を書き換えられない変数」
『中身を書き換えられない変数、って変くない?』
「『変くない?』よりは変じゃない」
『うー』
「で、もうひとつの #define の方。たとえば、この #define ののあとに」
int i = DEF_FLAG_OK;
「ってあった場合…… #define の本当の機能、なんだか憶えてる?」
『単語を置き換える! 前々回に far とかでやったから憶えてるもん』
「そうでした。そして、 #define はプリプロセッサ、つまりコンパイル前
に処理するんです。ってことは、コンパイルする前に」
int i = 1;
「に置き換えられるってこと」
『ええっ!?』
「って言っても、実際にファイルの中身が書き換えられるわけじゃないから
ね」
『あ、そういえばそうね。ってことは……〈書き換えられたとみなされる〉
ってヤツ?』
「そういうこと! プログラムはプリプロセッサにかけられて整形されて、
それが実際のプログラムとみなしてコンパイルされるんです」
『ほー。っつーことは、 const の方は普通のプログラムとしてコンパイル
されるんだ』
「そう、ちゃんとしたプログラムとしてね。ここで重要なのは、プリプロ
セッサが〈コンパイル前に処理する〉ってこと」
『どゆこと?』
「火美ちゃんが今言ったように、 const の方はコンパイルしたときにちゃ
んと残ってるから、エラーもそれなりのものになります」
const int DEF_FLAG_OK = 1;
DEF_FLAG_OK = 100;
『 const だからできないよね』
「だからこういうエラーが出ます」
error C2166: 左辺値は const オブジェクトに指定されています。
『なんとなくわかるね。で、これを #define に変えると?』
#define DEF_FLAG_OK 1 // 定数。
DEF_FLAG_OK = 100;
『あ、エラーが違う』
error C2106: '=' : 左のオペランドが、左辺値になっていません。
『左のオペランドって、 = の左側ってことだよね、だから DEF_FLAG_OK 』
「それが #define に置き換わるから?」
『ってことは……』
1 = 100;
『これでエラーになっちゃうんだ』
「直にこう書いても同じエラーが出るから。で、一番重要な点は、プリプロ
セッサは基本的に分かりにくい! ってこと」
『分かりにくい?』
「そう、だって見た目のプログラムが書き変わって、それがコンパイルされ
るんだもの。コンパイルエラーが出ても見つけにくいんだよね」
『そういえば、前に #define よりも const を使った方がいいって言ってた
けど、それが理由?』
「それが理由。プリプロセッサはできるかぎり使わない方向に向かってるか
な。でも!」
『でも?』
「プリプロセッサは自由度が高いから、昔からかなり色々使われてるんだよ
ね。今も使われてるし。たとえば TRACE() 」
『え、 TRACE() が!? 関数じゃないの?』
「関数じゃないんだよね、これが。 #define には、引数を付けることがで
きて、これが関数っぽくできるんです」
#define MACRO( x ) TRACE( "%d\n", x ) // マクロ
『むー、なんかよくわかんない』
「つまり」
MACRO( 100 );
「ってあったら」
TRACE( "%d\n", 100 );
「に置き換わるってこと。普通の #define A B だとスペースが A と B の
区切りだけど、 ( ) の付いたのだと ) が区切りになるから」
『 ) までのが、そっからあとの ) までと置き換わるってことね』
「こういう、 #define で作った関数っぽいのが〈マクロ〉って呼ばれるも
のです」
『これがマクロなのねー。 TRACE() なんかもこういうふうにして作られて
るんだ』
「基本的にはね。関数と違って、マクロは全部大文字で書くのが一般的」
『っつーか #define のは全部大文字じゃない?』
「あ、そうだね。でも const int をそうすることもあるかも。ま、それは
ともかく一番重要なのは、マクロを関数と同じように考えちゃダメ! って
こと」
『そなの? 普通に使えそうだけど』
「まず一番大きいのが、置き換えの副作用。まず関数の場合
void NanimoShinai( int p_i )
{
p_i;
p_i;
p_i;
}
「これをこう呼び出します」
int i = 100;
NanimoShinai( ++i );
TRACE( "%d\n", i );
『んーと、っつーか何もしてないんだから、 i が1増えて 101 なだけじゃ
ん』
「もちろん。で、これをマクロに置き換えてみます」
#define NANIMOSHINAIMACRO( x ) \
{ \
x; \
x; \
x; \
}
「あ、プリプロセッサは〈改行で終端〉ってみなされちゃうから、そうみな
して欲しくないときには \ を付けます。だからこれは」
#define NANIMOSHINAIMACRO( x ) { x; x; x; }
「と同じってことだから」
『 \ は実際には無視して考えればいいんだね』
「そゆこと。1行の方が〈どう置き換わるか〉は分かりやすいかな」
『で、これだとどう違う……あっ! i が 103 になってる!』
「実際に置き換えてみると分かるけど」
NANIMOSHINAIMACRO( ++i );
「が」
{
++i;
++i;
++i;
}
『げ! 3回も増えてる!』
「関数と違ってマクロはそのまんま置き換えるから、こういう問題が出てく
るわけ」
『関数だと、外と中じゃ全然違う世界だもんねぇ』
「さらに」
#define MAKEERROR( x ) \
{ \
x; \
X; \
x; \
}
『あ、大文字と小文字で間違ってる!』
「呼び出す部分は」
MAKEERROR( 100 );
「ビルドして」
『ほい。やっぱしエラー出たね』
「ここで重要なのは、エラーの出る行」
『行……? MAKEERROR() 呼び出してる行に出てるね』
「もし関数なら、 X; \ の行にエラーが出ます」
『そういえば!』
「これは大きな問題。これじゃ、 MAKEERROR() のどこが問題なのか分から
ないでしょ」
『ホントね、 MAKEERROR() の真ん中へんでエラーが出てるはずなのに』
「というわけで、マクロは一見関数に似てるけど、実はかなり違うものなの
です。というわけで次回に続く!」
/*
Preview Next Story!
*/
『もしかして、水希ちゃんてマクロ嫌い?』
「うん、僕はマクロ反対派」
『反対派とか賛成派とかあるの?』
「あるよ。そりゃもう、 ML や掲示板で、血で血を洗う争いが」
『うわぁ』
「というわけで次回」
< Version 6.06 プリプロセッサ色々 >
『につづく!』
「ま、最近はマクロくらいで論争にはならないだろうけど」
『じゃあ最近はサンマとかクジラとかで?』
「魚のマグロ違う〜っ!」