今回は分割コンパイルについて説明します
分割コンパイルとは複数のC/C++ソースファイルをまとめてひとつの実行ファイル(.exe)を作成する方法です
いままではひとつのソースファイル(main.cpp)だけでプログラムを作成してきました
VC++では複数のソースファイル(.cpp)に関数、クラスなどを分割することで
他のプロジェクトでそのソースファイルを使いまわしたりすることができ、関数やクラスを再利用することができます
再利用できるよく使う機能などをまとめたソースファイルなどをライブラリと呼びます
(正確にいうとソースファイルだけじゃないけど・・・)
プログラムの規模が大きくなってきたときやライブラリを作成する、
あるいは複数人で作業を分担するときにはソースファイルを分けて作業する必要がでてくるので必ず覚えてください
前提としては
main関数(もしくはWinMain関数)は必ず1つのプロジェクトにつき1つしかありません
というのはmain関数がプログラム全体の開始となる関数なので
複数のソースファイルにそれぞれmain関数があると「どこから開始するの?」ってコンパイラに怒られます
なので、ライブラリにはmain関数は含めません
では、早速分割コンパイルをやってみましょう
いつものように空のプロジェクトを作成します
空のプロジェクトの作り方はVC++便利機能で説明しています
まず、ヘッダーファイル(.h)を作ります
ヘッダーファイルとは#include <stdio.h>とかのアレです
フォルダ内で右クリック→新規作成→テキスト(.txt)
でまず、テキストファイルを作成します
拡張子が表示されてない人は
Windows XPの人は「ツール」→「フォルダオプション」
Windows Vista、Windows 7の人は「コントロールパネル」内の「デスクトップのカスタマイズ」→「フォルダオプション」
で開き、「表示タブ→登録されている拡張子は表示しない」のチェックをはずして
拡張子を表示するようにしてください
この設定をすることで後のファイルの名前の変更で拡張子を変更することができます
ファイルの種類の識別がしやすいのでセキュリティ的にも表示したほうがよいです
作成したファイルを右クリック→名前の変更で拡張子(.以下)を.txtから.hに書き直してやります
ここではtest.hにでもしましょうか
拡張子はファイルの種類を識別するためのもので
実際にはファイルというのは「テキスト形式」もしくは「バイナリ形式」の2種類しかありません
拡張子に惑わされないでください
「テキスト形式」は文字で書かれているファイルのことです
「バイナリ形式」は画像ファイルや音声ファイルなどコンピュータが直接認識できるような数値列になっているデータを指します、
広義な意味では文字コードと呼ばれる数値を文字に変換している「テキスト形式」も包括していることになります
(文字化けが起きるのは読み込みの文字コードが合わないときに起きます)
プログラムを文字で書く、ソースファイルやヘッダーファイルは当然テキスト形式です
ここまで説明しておいてなんですが、
いちいち、テキストファイルを新規作成して拡張子変更してもよいのですが
VC++では簡易的に次の方法でソースファイルやヘッダファイルを新規作成できます
main.cppを作るときとまったく同じです
ここでtest.hを作成してください
また、さらにtest.cppを作成してください
追加したらそれぞれ次のコードを書いてください
ファイルが複数になると画面分割したほうが見やすくなって便利です
画面分割に関してはVC++便利機能を参考にしてください
test.hです
// test.h #ifndef _TEST_H_ #define _TEST_H_ // 自作関数 void MyFunc(); // 自作クラス class MyClass{ public: // コンストラクタ MyClass(); // デストラクタ virtual ~MyClass(); }; #endif
test.cppです
// test.cpp #include "test.h" #include <stdio.h> // 自作関数 void MyFunc(){ printf("自作関数\n"); } // コンストラクタ MyClass::MyClass(){ printf("自作クラス コンストラクタ\n"); } // デストラクタ MyClass::~MyClass(){ printf("自作クラス デストラクタ\n"); }
main.cppです
// main.cpp #include "test.h" int main(){ // 自作関数呼び出し MyFunc(); // 自作クラスインスタンス生成 MyClass myclass; return 0; }
書き終わったらソリューションのビルド(F7)を行います
次のように出てきたらビルド成功です
上の図でソースファイル(.cpp)毎にコンパイルされることに注意してください
また、コンパイル後にリンクされていることにも気づいてください
デバック無しで開始で実行できます(Ctrl+F5)
ちなみに、#include "test.h"と""で囲った場合、
プロジェクトのあるディレクトリからの相対パスでヘッダーファイルを指定します
よく使う #include <stdio.h>とかの<>で囲った場合
ディレクトリのパスが指定されているところからも探索します
ディレクトリのパスについてはVC++プロジェクトのプロパティの
追加のインクルードディレクトリの項目を参考にしてください
なぜ、うまくいくのかというのをわかりやすく説明するために
簡易的に適当な名前の新しい空のプロジェクトを作り、
次のコードをmain.cppに書いて
#include <stdio.h> void MyFunc(); int main(){ MyFunc(); return 0; }
(ビルドでなく)main.cppをコンパイル(CTRL+F7)してください
コンパイルは通ります
しかし、今度はビルドを通りません
リンカーエラーとなります
これは関数の中身を書いてないのでどのような処理をすればいいかわからないから
実行ファイルを作成できないのです
実はVC++ではコンパイルが成功したソースファイルは対応した.objファイルが生成されています
その.objファイルにシンボルと呼ばれるものが埋め込まれます
そのシンボルに基づいてリンカーは関数の実装部分と関連付けてくれます
つまり、ヘッダーファイルにはプロトタイプ宣言などを書き
それを#includeして、普通に関数を呼び出す記述をすれば、シンボルを埋め込むことができます
どの関数の実装部分を呼び出すかはリンカーが関数の戻り値や引数から
複数の.objファイル(と.libファイル)から自動的に判別してくれます
今回は関数やクラスの実装部分はtest.cppに書かれていることになります
ゆえにビルドする全てのファイルにおいて
全く同一の戻り値と引数を持つ関数や同名のクラス名が存在してはいけないことになります
ところでtest.hで#ifndefと#defineで定義している部分を
インクルードガードといいます
なぜ、こんなことをする必要があるのか?
というのを知るためには#includeについて知る必要が出てきます
実は#includeはプリコンパイル時(コンパイル前の処理)に
ただ単にファイルをまるごとコピー&ペーストして張り付けているにすぎません
気を付けてほしいのは#includeしたファイルに#includeがある場合、さらにそれも展開されます
たとえば、次の場合main.cppをコンパイルすると多重定義エラーになります
つまり同じクラスを多重に宣言してしまうことになるのです
ここでインクルードガードを付けていれば
#ifndef インクルードガード名 #define インクルードガード名 // 関数などの宣言はここにする #endif
1回め#includeするとき、インクルード名が#defineで定義され
2回め#includeされるとき、#ifndefと#endifで囲まれたところが無効化されます
インクルードガードはヘッダーファイル全てにつけておくとよいでしょう
ただし、
インクルードガードに使う名前はすべてのヘッダーファイルにおいてかぶってはいけません
理由は他のヘッダーファイルが#includeで展開するとき無効化されてしまうからです
これが起きるとエラーの原因を探すのがけっこうめんどうになります
(基本的にヘッダーファイル名に由来したものをつける習慣を付けると良いと思います)
また、VC++ではインクルードガードの方法に #pragma onceを使う方法があります
これは#includeで多重に同じファイルを展開しないことを
プリコンパイラに知らせているものです
たとえば、test.hは#pragma onceを使って次のように書きなおせます
// test.h #pragma once // 自作関数 void MyFunc(); // 自作クラス class MyClass{ public: // コンストラクタ MyClass(); // デストラクタ virtual ~MyClass(); };
ヘッダーファイルの先頭に書けばいいだけです
ただし他のコンパイラ(たとえばlinuxやMacのコンパイラ)では#pragma onceは存在しないので
他環境下では#ifndefと#defineを使って書く必要が出てきます
また、たまに拡張子に(.hpp)というのも見かけたりします
これは普通のヘッダーファイル(.h)と変わりません
ただ、実装を含んでいるヘッダーファイル(インライン実装、テンプレートクラス、関数等)には習慣として付けておくとよいでしょう