Version 6.07
条件コンパイルの世界
「今回は前回の続き。前回 #if defined や #ifdef を紹介しました」
『 #define されていたら〜ってヤツね』
「 C 言語をする上で、これは死ぬほど付き合わなきゃいけない相手です。
そして結構問題のある相手です」
『問題があるぅ!?』
「そう。ま、便利な部分もあるけどね」
『はっ、そういえば水希ちゃんは反マクロ派だった……』
「じゃ、まず一番メジャーな使われ方から紹介しましょう。 Windef.h を見
てみて。その最初に」
#ifndef _WINDEF_
#define _WINDEF_
『うん、書いてある』
「で、最後に」
#endif /* _WINDEF_ */
『書いてある』
「これはほとんどのヘッダーファイルに書かれてるもので、火美ちゃんが自
分でヘッダーファイルを作るときには火美ちゃんも書いた方がいいもの」
『そんな大事なんだ、なんでだろ』
「考えてみて」
『えっと、 #ifndef は #if !defined と同じだから _WINDEF_ が #define
されていなかったら、だね』
「そう」
『いなかったら、そのあとから……最後の #endif まで、えっと……そのま
ま残るんだよね』
「そのとおり! よくできたねー、こんがらがるでしょ」
『こんがらがる〜。でも、そのまま残るって、なんかそのままだね』
「うん、そういうもの。逆の場合だと」
『えっと、 _WINDEF_ が #define されてたら、そのあと……空白。ってこ
とは、 Windef.h のほとんどが消えちゃうって事? それってやばそう』
「それが、こうしないとやばいんだよねー。 _WINDEF_ ってどこで #define
されてる?」
『どこ……あ、 #ifndef _WINDEF_ のすぐ下でしてるじゃん』
「ってことは、 Windef.h が最初に読み込まれたときに #define _WINDEF_
されるってこと。で、2度目に Windef.h が読み込まれたら?」
『あ! そっか、そのときは _WINDEF_ が #define されてるから、 #ifndef
で Windef.h のほとんどが読み込まれない!』
「つまり?」
『つまり、こうすればヘッダーファイルがたった1度しか読み込まれなくな
る!』
「そういうことでした」
『へー、面白い! こーゆー仕組みに使うことできるんだー』
「 Ver 6.04 ( No.104 ) で〈2重にインクルードしても問題ない〉って
言ったのはこれが理由」
『ってことは、ほとんどのヘッダーファイルがそうなってるんだ』
「そうだよ。たとえば Debug プロジェクトの各ヘッダーファイルも」
#if !defined(AFX_STDAFX_H__D4FAC1E8_略_F492281A3E79__INCLUDED_)
#define AFX_STDAFX_H__D4FAC1E8_略_F492281A3E79__INCLUDED_
// 中身
#endif // !defined(AFX_STDAFX_H__D4FAC1E8_略_F492281A3E79__INCLUDED_)
『む、なんか変な数字か文字かなんか』
「 VC が自動生成したのだからね。普通は _WINDEF_ みたくヘッダーファイ
ルの名前に近いのにするけど、逆に近いと他のヘッダーファイルと重なっ
ちゃったりするから」
『だからこんな無茶苦茶なの?』
「そゆこと。これは各ファイルで違うようになってるから
『ふーん。あ、そうそう、質問!』
「はい、火美ちゃん」
『今回のふたつもそうだし、前回のこれ』
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
『もそうなんだけど、 #endif ってなんで最後に #if の条件がコメントさ
れてるの?』
「それは、ネストが判りにくいから。今回みたいな使い方だと、インデント
しようがないでしょ」
『ヘッダーファイル全体がインデントしちゃうね』
「だから、 #if と #endif の対応が判りにくいわけ」
『そこで対応する #if をコメントで書いとくんだ。でもなんか応急処置っ
ぽいね』
「ま、プリプロセッサそのものの分かりにくさってことかも。あとついで
に」
#pragma once
『そいやこれもプリプロセッサだね。どっかで見たような感じ……』
「 #pragma は〈プラグマ〉って読んで、色々便利なことができるプリプロ
セッサ。そのひとつが #pragma once で、こうするとこのファイルを1回し
か読み込まないようにしてくれます」
『へ? じゃー #if - #endif って要らないじゃん』
「そうもいかないんだよね。 #pragma って最近の開発環境じゃないと使え
ないから」
『それってもしかして、 #if _MSC_VER > 1000 と関係してる?』
「関係してます! まさにそのためのプリプロセッサ」
『ほ〜』
「この例みたいに、プリプロセッサには〈場合によってプログラムを変える
機能〉もあります」
『つまりこの例だと、 _MSC_VER って組み込みマクロが、 1000 以上に置き
換わるんなら、 #pragma once をプログラムに置きましょう、ってことだよ
ね』
「そういうこと。この例では組み込みマクロをフラグにしてるけど、それ以
外の #define されたものをフラグにすることも多いです」
『たとえば?』
「自分で #define するものとか。ライブラリの中には #if がいっぱい入っ
てて、自分で #define をすることでライブラリを書き換えることができま
す」
『つまりスイッチになるわけだ』
「他に #define 以外でも #define と同じようなことができます。【プロ
ジェクト】−【設定】ダイアログ開いて」
『ほい、いつものね』
「【プロジェクトの設定】ダイアログの【C++】ページ、カテゴリ【一般】
の【プリプロセッサの定義】の項目を見て」
WIN32,_DEBUG,_WINDOWS,_AFXDLL,_MBCS
「ここに書き込んだのはすべて #define されたものとして判断されます」
『つまり #define WIN32 ってなってるんだ。でもわざわざここでする必要
ある?』
「あるよ。この〈プロジェクトの設定〉っていろんな種類作れるから、それ
ごとに #define を変えることができるからね。左上の【設定の対象】を
Win32 Release に変えると……」
WIN32,NDEBUG,_WINDOWS,_AFXDLL,_MBCS
『を、かなり変わった!』
「これで #define がかなり変わるから、ライブラリの中身もかなり変わる
わけ」
『へー』
「こういうのを〈条件コンパイル〉っていいます」
『場合分けしてコンパイルするわけねー』
「ただ……」
『あ、水希ちゃん、ダークモードだ』
「確かに、この条件コンパイルはばしばし使われてます。たとえば、
Ver 6.03 ( No.103 ) で紹介した near と far 」
『普通は空白、ウィンドウズ 3.1 だったら……そっか! その〈 3.1 だっ
たら〉が条件になるんだ!』
「そゆこと。 MFC を含めて、ライブラリはこの条件コンパイルを使って、
いろんな環境で使えるようになってます。でも!」
『……でも?』
「最初はこういうのはやめた方がいいよ。プリプロセッサは、実際にどの
コードが使われてるかって分かりにくいからね」
『確かに、 #ifdef の分岐って〈それが #define されてるか〉だから、普
通の if とかよりも分かりにくいね』
「だから、当分はこういうのは使わない方がいいかも」
『当分っていつまで?』
「ま、使うなって言ってもマクロに凝ったりする時期って必ずあるから、そ
れで痛い目に遭ってから数年後ってとこかな」
『なんかそれって危ない青春時代みたいな変な表現……』
「っつーか実際、プリプロセッサやマクロは便利だから、使い始めるとどん
どん使う傾向にあるんだよね」
『たとえば?』
「たとえば、さっきのウィンドウズ 3.1 のとか。これは使わないけど、も
少し話を進めて、ウィンドウズとマックと両方で使えるプログラムとか」
『そいやマックでもアプリ作れるわけだからねぇ』
「で、自分で作ったのが、どっちの OS でも使える!」
『それってむっちゃかっこよくない!?』
「でしょう。そういうのがプリプロセッサを使うとできるわけ」
『そっかー、それだとはまっちゃいそうだね』
「他にも、プリプロセッサならではの機能とかあったり。たとえば __LINE__
ってマクロ」
『なんか妙なのだね』
「これは、その行の行番号に置き換えられるんです」
TRACE( "%d\n", __LINE__ );
『ほー、 TRACE() した行の行番号が出た!』
「 _MSC_VER なんかと同じ組み込みマクロのひとつで、自動的にその行の行
番号に置き換えてくれます」
『これは便利だね。エラーが出たときとかこれで報せられるもんね』
「さらに、たとえばこれを使うマクロ」
#define TRACE_LINE TRACE( "%d\n", __LINE__ )
『 TRACE_LINE って書けば上のと同じのができるわけだ』
「これは関数じゃダメでしょ」
『そっか、関数だとその関数の中の行番号が出ちゃうんだ!』
「これはマクロの〈置き換える〉って機能を活用した例。他にも、今度解説
するけど TRACE() もマクロ」
『そいやそんなこと言ってたね』
「上の【プリプロセッサの定義】に _DEBUG ってあるでしょ。実はこれが
#define されてなかったら、 TRACE() は空白に置き換えられちゃうんです」
『つまり消えちゃうってこと?』
「そゆこと。 TRACE() はデバッグ用だから、リリース用のからは削除する
んでこういう機能が付いてるんです」
『ほー。要するに便利ってことね』
「う”……あくまで、便利な部分もある! って話」
『はいはい』
「〈2度読み込み〉は #pragma once で、 #define 定数は const int で、
まだ教えてないけど関数似のマクロは inline 関数やテンプレートで、って
感じに置き換えられるんです」
『つまり、実際にはプリプロセッサは使う必要なし?』
「少なくとも自分で書く分にはね。プリプロセッサよりも安全なものを使っ
ていくことにしましょう!」
『はいはい』
/*
Preview Next Story!
*/
『でも結局、そのテンプレートとかって知らないとダメってことよね』
「プリプロセッサは自由度高くて、少し知ってるだけで応用できるからね」
『じゃープリプロセッサの方がいいんじゃん』
「そうじゃないんだってばー」
『というわけで次回』
< Version 6.08 バグも色々 >
「につづく!」
「外科医がメスをペーパーナイフ代わりに使っちゃまずいでしょ?」
『出た水希ちゃん必殺の医療系たとえ!』
「ちゃかさないでーっ!」