Version 15.02
関数とリンク
「前回はソースファイルごとのコンパイルについて見てみました」
『一番前に表示してるソースファイルがコンパイルされて、その
ソースファイルの名前のオブジェクトファイルが作られるんだよね』
「そういうこと。で、その複数のオブジェクトファイルをリンクしてくっつ
けることで、 Exe ファイルが作られます」
『ってとこまではまだできてないんだけどね』
「そうだね。というわけで、まず Sub.cpp をこう修正して」
#include <Windows.h>
void Sub()
{
MessageBox( NULL, "サブ", "Sub()", MB_OK );
}
『なんか WinMain() とほとんど同じね』
「分かりやすくするために、ってことで。さて、この関数を WinMain() か
ら呼び出すってことを考えてみます」
『んー、今までの例で言うと、ヘッダーファイルに関数の宣言を書けばいい
んじゃない? Version 2.12 ( No.023 ) みたいに』
「それが一番標準的な方法だね。ってことで、 Sub.h を作って」
『ほい。【ファイル】−【新規作成】で【ファイル】ページを開いて、
【C/C++ ヘッダーファイル】でファイル名は Sub.h 、っと』
「そこに、 Sub() の宣言を書きます」
// Sub.h
void Sub();
「そして、それを 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
)
{
Sub();
return 0;
}
「まず、 Sub.h をインクルードします」
#include "Sub.h"
「そしたらもう呼べるので、呼び出します」
Sub();
『簡単ねー。ビルドして実行、お、 Sub() ダイアログ出た』
「で」
『で?』
「一見簡単に何気なく呼び出せてるように見えますが、この関数呼び出しに
は複雑な背景があるんです」
『そうなの? なんか全然普通に呼び出せてるけど』
「というわけで、ひとつずつ試してみましょう。 Debug フォルダの中を空
にして」
『ほい、ファイル削除っと」』
「次に Sub.cpp の中の Sub() をコメントアウトしてください」
// Sub.cpp
#include <Windows.h>
/*
void Sub()
{
MessageBox( NULL, "サブ", "Sub()", MB_OK );
}
*/
『……へ? コメントアウト?』
「そう。つまり関数そのものを削除ってこと」
『そんなことしちゃまずいんじゃ……』
「そのまずくなる所を細かく見ようっていう話。まず、この Sub.cpp を
コンパイルして」
『ほい、コンパイルっと。……通った……』
「そう、まずこの時点でコンパイルエラーにはなりません」
『なんか、コンパイルエラーになりそうだったんだけど……』
「では次、 Main.cpp をコンパイルして」
『ほい。 Main.cpp を手前に持ってきてコンパイルっと……え……ええええ
ええ!? 通っちゃった!! なんでコンパイルエラーにならないの!?』
「そう、コンパイルエラーにならないんです。じゃ、最後にリンクしてみ
て」
『う、うん。ビルドっと……あ、良かった、エラー出た……』
--------------------構成: BuildTest - Win32 Debug--------------------
リンク中...
Main.obj : error LNK2001:
外部シンボル ""void __cdecl Sub(void)" (?Sub@@YAXXZ)" は未解決です
Debug/BuildTest.exe : fatal error LNK1120: 外部参照 1 が未解決です。
link.exe の実行エラー
BuildTest.exe - エラー 2、警告 0
『でも、リンクの時にエラーが出るなんて……』
「なんでこうなるのか、をひとつずつ見ていきます。まず、話を分かりやすく
するために、話を Main.cpp に絞ります」
『 Sub.cpp は無視?』
「そう、無視。と、本当に無視してみようか」
『本当に無視?』
「ワークスペースの【FileView】の、【BuildTest】−【Source Files】を
開いてみて」
『うん。 Main.cpp と Sub.cpp が入ってる』
「この Sub.cpp を右クリックして」
『あ、メニューに【コンパイル】とかある!』
「それは置いといて、メニューの中から【設定】を選んで」
『ほい。【プロジェクトの設定】ダイアログが出たよ』
「右側の【一般】ページを開いて、【このファイルをビルドしない】を
チェックして」
『ほい。あ、アイコンが変わった……』
「下向き矢印が消えたでしょ。【OK】ボタン押してダイアログ閉じて、
Main.cpp を次のように修正して」
// Main.cpp
#include <Windows.h>
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
return 0;
}
「これでリビルドしてみて。通るでしょ」
『あ、ホントだ……』
「さっきの【このファイルをビルドしない】をチェックすると、その
ソースファイルはコンパイルされないんです」
『本当に無視するってこーゆーことなんだ。 Debug フォルダに Sub.obj も
ないし』
「さて、無視できたところで、 Main.cpp を今度はこう修正してみて」
// 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() 関数の本体……がない、っていうのがつまりさっきのと同
じ状態ってこと?』
「そういうこと。関数の本体、つまり定義がなくて、宣言があるだけ」
『それでも……』
「そう。 Debug フォルダの中身を空にして、コンパイルしてみて」
『……通った』
「実は、関数を呼び出す時っていうのは、関数本体じゃなく、宣言を見てる
んです」
『ええっ!?』
「宣言がありさえすれば、コンパイルは通るんです」
『でも、リンクは通らない……』
「実はリンクでしているのは、関数の結び付けなんです。呼び出している所
と、関数本体をリンクの時に結び付けるんです」
『その時に本体がないからリンクでエラーになる!』
「それでこのエラー」
Main.obj : error LNK2001:
外部シンボル ""void __cdecl Test(void)" (?Test@@YAXXZ)"
は未解決です
「 Main.obj は関数を呼び出してるソースファイルのオブジェクトファイル
で、【外部シンボル】はその呼び出している関数のこと」
『そのあとの長いのが……』
「 "void __cdecl Test(void)" は?」
『あ、普通に関数だ。 __cdecl って Version 8.01 ( No.143 ) でやったの
だし』
「 (?Test@@YAXXZ) は、その関数の特殊な書き方」
『まー Test ってのは関数名なんだろうけど』
「で、この外部シンボル、つまり関数が見つからないので【未解決】ってこ
と」
『なるほど……』
「リンクするときに関数を探しているわけだけど、この探す相手が、
コンパイルしてできたオブジェクトファイル全部」
『え?』
「というわけで、まずさっきの Sub.cpp をコンパイルできる状態に戻して」
『さっきの【このファイルをビルドしない】のチェックを外すってこと?』
「そういうこと。そして Sub.cpp をこう修正して」
// Sub.cpp
#include <Windows.h>
void Test()
{
MessageBox( NULL, "サブ", "Sub()", MB_OK );
}
『って、ほとんどさっきと同じ……あ、呼び出す関数が Test() になって
る』
「これでリビルドすれば」
『【Sub()】ダイアログが出た!』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『こうやってちゃんと理解していくことが大事?』
「そ。 LNK2001 のエラーが出ても、ちゃんと解決できるでしょ」
『今までだと、適当に色々試してうまくいけばいい、ってしてたかも……』
「次回はインクルードファイルについて正確な理解をしてもらいます」
『宣言を読み込むだけじゃないの?』
「実はソースファイル代わりにもなるんです」
『ええっ!?』
「というわけで次回」
< Version 15.03 インクルードはただの置き換え >
『につづく!』
「ほとんどヘッダーファイルだけでプログラムを組むこともできます」
『う、それはなんか嫌だ』