Version 15.03
インクルードはただの置き換え
「前回は、関数の呼び出しはリンクするときに解決するんだってことを説明
しました」
『あれにはビックリしたー。関数って、宣言さえあれば一応呼べちゃうの
ね』
「コンパイルは通るからね。さて現在、ソースファイルとヘッダーファイル
は次のようになっていると思います」
// Main.cpp
#include <Windows.h>
void Test();
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
Test();
return 0;
}
// Sub.cpp
#include <Windows.h>
void Test()
{
MessageBox( NULL, "サブ", "Sub()", MB_OK );
}
// Sub.h
void Sub();
『そういえば、 Sub.h の Sub() の宣言ってほっといていいの?』
「宣言だけあって定義のない関数があっても、その関数を呼びさえしなけれ
ば大丈夫。関数を呼んじゃうと、リンクするときに定義がないからエラーに
なるけど」
『呼びさえしなければ問題ないんだ。あ、もうひとつ! Test() の宣言っ
て Sub.h になくていいの?』
「そう、そこがちょっと分かりにくい所かも。普通、関数の定義は
ソースファイルに、宣言はヘッダーファイルに書きます」
『だよね』
「でも、それは〈便利〉だからそうしてるだけで、必ずしもそうしなくてい
いんです」
『え”、そうなの?』
「というわけで、そもそもヘッダーファイルってなんなのか、って考えてみ
ます」
『ヘッダーファイルがなんなのか……?』
「まず、これを試してみて」
// Main.cpp
#include <Windows.h>
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
MessageBox( NULL, "メイン", "WinMain()", MB_OK );
return 0;
}
『ん、フツーに実行できるよ。一番最初の例と変わらない……』
「では次。同じく Main.cpp をこう修正して」
// Main.cpp
#include <Windows.h>
#include "Sub.h"
『えーっと…… WinMain() がないよ?』
「というわけで、 Sub.h に追加します」
// Sub.h
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
MessageBox( NULL, "メイン", "WinMain()", MB_OK );
return 0;
}
『ええーっ!? ま、まさか……うわ、ビルドして実行、【メイン】
ダイアログ出たよ……どうして?』
「理由は簡単。 Main.cpp で Sub.h をインクルードしてるでしょ。実は、
インクルードは〈インクルードしたファイルと置き換わる〉ってことなんで
す」
『置き換わる……』
「つまり、実際にはこうなってるってこと」
// Main.cpp
#include <Windows.h>
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
MessageBox( NULL, "メイン", "WinMain()", MB_OK );
return 0;
}
『うわ、同じ。つまりコピペされてるみたいなもんなのね……』
「そう、そこが重要。まず、今のインクルードの説明で、最初の Test()
関数の宣言場所の話は分かったでしょ」
『そか、 Sub.h に書かれてても、実際にはインクルードした Main.cpp の
頭に書かれてるのと同じだから、関係ないんだ』
「そういうこと Main.cpp が」
// Main.cpp
#include <Windows.h>
#include "Sub.h"
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
Test();
return 0;
}
「で、 Sub.h が」
// Sub.h
void Test();
『でも、インクルードしてるんだから』
// Main.cpp
#include <Windows.h>
void Test();
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
Test();
return 0;
}
『と同じってことなんだ』
「だから、関数の宣言は別にヘッダーファイルでしなくてもいいってこと。
Test(); って呼び出す前に出てくればいいだけ」
『? 前に出てこなきゃダメ?』
「そう、だめ。たとえば」
// Main.cpp
#include <Windows.h>
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
Test();
return 0;
}
#include "Sub.h"
「ってすると、コンパイルエラーになります」
Main.cpp(11) : error C2065: 'Test' : 定義されていない識別子です。
「そういえば、 #include みたいに、頭に # がついているものってなんて
言うか憶えてる?」
『プリプロセッサだよね、 Version 6.05 ( No.105 ) でやった。確か、
コンパイルするまえに……コンパイルする前?』
「そう、インクルードっていうのはコンパイル前にされるんです。だから、
イメージとしては、 Main.cpp は、コンパイル直前に、 Windows.h と
Sub.h の中身がそのまま書き込まれていると思ってください」
『インクルードしたファイルがそのまま書き込まれる……そしてそれが
コンパイルされる!』
「そのコンパイルは、上から順に見ていきます。そして、関数宣言があれば
記憶して、関数呼び出しがあれば、憶えた関数宣言の中にあるかどうか調べ
て、あればリンク用に取っておいて、なければさっきの C2065 」
『上から見ていって、宣言があれば憶えておく……』
「しかも、これはコンパイル単位でするから……」
『だから?』
「次回に続く!」
/*
Preview Next Story!
*/
『ヘッダーファイルって、ソースファイルにもなるのねー』
「でもそうすると不思議に思わない?」
『ヘッダーファイルなんていらない!』
「そういうこと。でもヘッダーファイルがあると便利なんです」
『なんで?』
「というわけで次回」
< Version 15.04 ヘッダーファイルは必要? >
『につづく!』
「残っているものにはちゃんと役割があるんです」
『深く考えずに使っちゃってるけどそういうもんなのね』