Version 15.06
ヘッダーファイルを2度読み込む?
「前回は、ヘッダーファイルをインクルードする順番について説明しまし
た」
『ちゃんと順番を考えてインクルードしないとダメなのよね……』
「でも、それ以外にやっかいな問題がヘッダーファイルにはあるんです」
『げげ』
「ヘッダーファイルはソースファイルの代わりになるって説明しました」
『単なる置き換えなんだもんね』
「だから、こんなこともできます」
// Sub.h
#include <Windows.h>
『げ、ヘッダーファイルでインクルードしてる!!』
「実際、前回のように〈インクルードする順番〉が関係してくる場合、
ヘッダーファイル内で〈必要なヘッダーファイルをインクルードしておく〉
というのはよく使われるパターンなんです」
『そか、そうすれば順番気にするどころか、 Typedef.h をインクルードし
なきゃって考える必要もないんだ。なんだ便利じゃない』
「でもはたしてそうでしょうか、というのが今回の話」
・循環参照
『じゅんかんさんしょう?』
「簡単に言うと、蛇が自分のしっぽを食べるような感じ」
『ぐるぐるぐるぐる……』
「たとえば Sub.h をこんなふうにしたり」
// Sub.h
#include "Sub.h"
『げ、 Sub.h が Sub.h インクルードしてる! これやるとずーっと
インクルードし続けちゃうわけね』
「実際にはこういうコンパイルエラーになります」
fatal error C1076:
コンパイラの制限 : ヒープの領域を使い果たしました;
上限を設定するために /Zm オプションを使用してください。
「ヒープっていうのはメモリ領域のこと。コンパイルする時、
ソースファイルやヘッダーファイルをメモリに書き込んでそれをコンパイル
するんだけど、そのメモリが足りなくなったってエラー」
『永遠にインクルードされるんだから当然なるわよねー。でもこれ、絶対あ
り得ないと思うんだけど』
「じゃあこれは? Sub.h はこうで」
// Sub.h
#include "Typedef.h"
「 Typedef.h はこう」
// Typedef.h
#include "Sub.h"
『あー……複数のヘッダーファイルを経由してぐるっと一周……』
「こういうことがあるから、ヘッダーファイルの中で他のヘッダーファイル
をインクルードするときには細心の注意が必要、かな」
『てかこれやっちゃいそう……』
「次は、二重定義」
・複数のヘッダーファイルでの二重定義
「まず Main.cpp がこう」
// Main.cpp
#include <Windows.h>
#include "Typedef.h"
#include "Sub.h"
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
return 0;
}
「次に Typedef.h 」
// Typedef.h
struct DATA
{
int m_i;
};
『あ、構造体だ、懐かし〜』
「最後に Sub.h 」
// Sub.h
#include "Typedef.h"
『あ、 Typedef.h をインクルードしてる……けど、今回は循環参照じゃな
いよね。なら大丈夫そうだけど』
「全然大丈夫じゃないんです」
『へ? ビルド……あ、エラー』
typedef.h(4) : error C2011:
'DATA' : 'struct' で示される型としてすでに定義されています。
「つまり、構造体 DATA がふたつもあるから」
『すでに定義されているのに再定義しようとしたからエラー……』
「これはとても良くあるパターンなんです。ヘッダーファイルから
ヘッダーファイルをインクルードする、ってすると簡単に起きます」
『やっぱりそれって良くないのねー』
「でも実は、 API やランタイムでは、二重に読み込むなんて普通にあるこ
となんです」
『? それってつまり、回避策があるってこと?』
「そういうこと。多分火美ちゃんも見たことあると思うけど」
// Typedef.h
#ifndef _TYPEDEF_H_
#define _TYPEDEF_H_
struct DATA
{
int m_i;
};
#endif // _TYPEDEF_H_
『あ! 見たことある! この #ifndef 使ったの!!』
「 Stdafx.h とかの、 MFC 使う時に作られるヘッダーファイルは全部こう
なってるし、 API や ランタイム、 MFC のヘッダーファイルもこうなって
るでしょ」
『うん、そういうので見たことある』
「それに、 Version 6.07 ( No.107 ) で」
『そっか、 #ifndef のプリプロセッサ教えてもらったときにやったんだ
ね』
「もう一度簡単に説明すると」
#ifndef _TYPEDEF_H_
// 中身
#endif // _TYPEDEF_H_
「は、 _TYPEDEF_H_ が #define て定義されて〈いない〉場合にのみ
〈中身〉が有効になります。もし _TYPEDEF_H_ が定義されていたら、
〈中身〉は空白に置き換わります」
『なくなっちゃうわけね』
「初めてこのヘッダーファイルがインクルードされた時には、 _TYPEDEF_H_
は定義されていないので〈中身〉が有効になります。その中に」
#define _TYPEDEF_H_
「があるので、この時点で _TYPEDEF_H_ が定義されます」
『2度目に読み込まれた時には、 _TYPEDEF_H_ が定義されているから
〈中身〉が空白に置き換えられるから、再定義されない、ってわけね』
「そういうこと。ただ、これはひとつ問題があります」
『問題?』
「 Typedef.h ってファイルが複数あったら?」
『あ……両方とも _TYPEDEF_H_ になっちゃったらまずいね』
「 MFC の場合は特殊なコードを付けて回避しています。自分で作るときに
はライブラリ名とかを頭に付けるといいかも」
『それでもちょっと不安かも』
「そういう場合のために、もっとスマートな方法もあります」
// Typedef.h
#pragma once
struct DATA
{
int m_i;
};
「 #pragma は特殊な機能を持つプリプロセッサで、 once を指定するとそ
のヘッダーファイルは1回しか読み込まれなくなります」
『でもさ、あまり使われてないよね』
「 #pragma そのものの知名度が低いからね。そうだね、今までのヘッダー
ファイルの仕組みを聞いて、どう思った?」
『なんかすんごくめんどい。インクルードの順番とか循環参照とか、そうい
うの気にしなきゃいけないのが』
「でしょう。実は、他のプログラミング言語では、そういうの気にしなくて
いいんです」
『へ?』
「ヘッダーファイルは〈置き換え〉じゃなくなってるし、インクルードも何
度でもしていいし、順番も関係なし。仕組みそのものがもっとスマートに
なっているんです」
『つまり、 C++ って古くてダメってこと?』
「だめじゃないけど、古いのは確か。だから、こういう手作業で面倒なこと
が必要になってくるし、新しいプログラミング言語に追い付こう、ってこと
で #pragma が追加されたりしたんです」
『あ、 #pragma ってあとから追加されたんだ』
「そう、比較的最近の機能なんです。だからあまり知られてないのかな。
ヘッダーファイルって、単に使う関数のをインクルードすればいい、って
イメージだったかもしれないけど」
『全然違う』
「それを踏まえて使わないと大変、ということで」
/*
Preview Next Story!
*/
「以上が、ソースファイルやヘッダーファイルまわりの基礎」
『基礎ってことは、これから応用?』
「そう、たとえば DLL とか」
『 DLL !! あの DLL 、 System32 フォルダにコピーしたりする』
「そう、その DLL 」
『その DLL を……?』
「というわけで次回」
< Version 15.07 スタティックリンクライブラリと DLL >
『につづく!』
「もちろん火美ちゃんが作ったり使ったり」
『いや無理それ! 難しすぎ!!』
「まぁ実際の所」
『簡単?』
「やっぱり難しいけど」