Episode.1 #defineを「使うな」と「使え」
読んで無い人は先にこちらをどうぞ -> Episode.0
なぜか、#defineは「使うな」と言われたり、「使え」と言われたり、扱いに困ってしまいます。でもうまく使えば見やすく、簡潔なプログラムが書けるようになります。私の認識の経緯を絡めながら#defineを理解していこうと思います。
初めての「使え」
C言語を習うと、必ずマジックナンバーということを聞くと思います。
1 int array[50]={}; 2 int i; 3 for ( i=0; i<50; i++ ) { 4 array[i] = 100; 5 }
たとえば上記のようなプログラムがあったとして赤文字の部分がマジックナンバーで、それを#defineで記述しろってことが#defineとの最初の出会いでした。これはこれで理解できます。*1
初めての「使うな」
逆に、私が始めて#defineを「使うな」ということを聞いたのは、専門2年でC++を習ったときに、定数は#defineを使わずにstatic constを使えといわれたときでした。
当時は「ふーん、C++ではstatic constを使うんだ」という単純馬鹿な脳内変換で自分を納得させ、それ以上突っ込まずにいました。
#defineマクロの有効利用
会社に勤め始めて、先輩の人のソースとかを見ていると、
1 struct table_t tables[TABLE_MAX]={{}}; 2 struct table_t *table; 3 ARRAY_LOOP( tables, table ) { 4 look_drawer( table ); 5 table->count++; 6 }
というような記述を見つけました。はて、これは何をやっているのでしょうか?
ARRAY_LOOPの宣言を見ると
1 #define ARRAY_ELEMS(_a) (sizeof(_a)/sizeof(_a[0])) /*2*/ 2 #define ARRAY_LOOP(_a,_b) /*3*/ \ 3 int i; \ 4 for ( i=0, _b=&_a[i]; i
配列一つ一つを取得して処理するforループを#defineマクロをうまく活用して表現されています。
これが#defineマクロと呼ばれるものです。同じような処理を毎回書くのは面倒なので、マクロにしてしまうという方法です。
もう一つの#defineマクロ
#define PLUS(x,y) ((x)+(y))
のような基本的なマクロの応用を上で紹介しましたが、これ以外にもマクロには使い方が存在します。
1 #define CAST(_c,_v) (_c)_v 2 int a=1; 3 char b = CAST( char, a );
この例では、charという変数でなく型名をマクロに渡しています。
1 #define SET(_m,_v) param._m = _v 2 struct { int x,y,z; } param; 3 SET( x, 1 ); 4 SET( y, 5 ); 5 SET( z, 8 );
ちょっと極端ですが、今回は構造体のメンバ名をマクロに渡しています。
だんだん#defineマクロがわかってきた
ここまで書けば、わかると思いますが、#defineマクロとはただの「置き換え」であることがわかります。
上の例でいうと
b = CAST( char, a );
は
b = (char)a;
に
SET( x, 1 );
は
param.x = 1;
に
置き換えてくれるということになります。
もしparamがポインタに変更になったときなどに修正が簡易になりますね。
二度目の「使うな」
- 作者: ブライアンカーニハン,ロブパイク,Brian Kernighan,Rob Pike,福崎俊博
- 出版社/メーカー: アスキー
- 発売日: 2000/11
- メディア: 単行本
- 購入: 58人 クリック: 1,152回
- この商品を含むブログ (209件) を見る
この本を読んでいると、#defineは悪しき物だから、「使うな」と明記されています。マジックナンバーにはenumを使い、マクロはinline関数*4で代用しましょうと書いてあります。
ΩΩ Ω<な、なんだってー
おまえが作ったものだろー!と突っ込みたくなりますが、理由も明確に記してあります。
例えば2*3を2乗したいと考えたときに
1 #define POWER(x) ((x)*(x)) //2乗する 2 int a = POWER(2*3);
のように書いたとします。
この場合置き換えると
a = ((2*3) * (2*3)); となります。
一見良いように見えますが、2*3を2回してしまっていて無駄な時間が発生してしまっています。
これをinline関数にすると
1 inline int POWER(x){ return (x * x); } 2 int a = POWER(2*3);
でも「使え」
しかし、#defineマクロを完全否定しているわけではなく、上記のように複数回参照するようなマクロ以外なら「使ってもよろしい」というのがカーニハンの考えのようです。
ARRAY_ELEMSマクロは実際にこの本に書いてあり、このような使い方はしてよいとかいてあります。
それに、CASTマクロやSETマクロはinline関数では実現できないものです。こういった#defineマクロは有効活用していくべきだと思います。
定数をenumにしたけど・・・
確かに、enum型は定数として使え、グループ化出来るのでこちらを使ったほうがより自然です。が、小数や文字列が指定出来ません。これを#defineで記述していた私としては#defineを使うべきか、static constにするのかどちらがよいのということが出てきます。
私の考えとしては#defineをマクロとして使うのなら、混同させないためにstatic constを使用したほうが良いと思います。
これで、C++の先生の言っていたことが少しはわかったような気がします。
これ以外にも
#defineを使った構文は様々にあります。私もまだ全てを知らないので、知ることが出来たときは随時報告していきたいと思います。
追記(20060531) ##x##構文
1 int check_int_func( int x ) {...} 2 short check_short_func( short x ) {...}; 3 char check_char_func( char x ) {...}; 4 #define CHECK_FUNC(_type, _v) check_##_type##_func( (_type)(_v) ) 5 int function( int arg ) 6 { 7 CHECK_FUNC(int, arg); 8 CHECK_FUNC(short, arg); 9 CHECK_FUNC(char, arg); 10 return 0; 11 }
こんな感じで*7、関数名の間を置き換えたい等の事象が生じた場合に、#defineの引数を##で挟むことにより、置き換えることができるようになります。
以上!
#defineのお話は一旦終了です。どうでしょうか?#defineを理解出来たでしょうか?コメントで質問してくれれば極力答えます!
今回は長くなりましたが、普段はこんなに書かないつもりなのでよろしくです。
それでは、このエントリがどこかの誰かの助けになれたらと願いつつ終わります。