2012年7月30日月曜日

分割コンパイルの仕組みとか

他のファイルに書いた機能を使いたい時は

#include "ファイル名.拡張子"


という風に書いてファイルをインクルードします。
そうするとビルド時、前処理としてその場所にファイルが読み込まれ、それからコンパイルが行われます。

インクルードの仕組みは単純で、つまりこの時、単にソースコードが複製されているのだと言えます。

コンパイルはファイル毎に行われるので、インクルードされたファイルはその場所でコンパイルされますが、さらに元々のファイルでもコンパイルされるのが普通です。

変数や関数などを使う時、宣言は複数あっても良いが、定義は1度だけというルールがあります。

だからもしインクルードで読み込んだファイルと読み込む側に同じ定義があればコンパイルエラーになります。

さらにインクルードで読み込んだファイルに何らかの定義があれば、例え読み込む側に同じ定義がなくてもリンクエラーになります

インクルードで読み込んだファイルに定義があれば読み込む側と読み込まれる側の両方で定義が発生しますが、 コンパイルの段階ではファイル毎に別々に処理が行われるので問題がありません。
しかしリンクの段階で、インクルードによって複製されたコードが統合されるので、2重定義となってしまうのです。


だから通常は宣言だけを書いたヘッダファイルと定義を書いたファイルに分けて、ヘッダファイルを複数のファイルからインクルードするようにします。(VC++ではヘッダファイルの拡張子は.h、定義などの通常のソースコードの拡張子は.cppにします。本来、拡張子はファイルの内容に影響を与えませんが、開発環境がそれを区別するという事はあり得ます。)

  

コンパイラの役割

上でも書きましたが宣言は複数あっても良いが、定義は1度だけというルールがあります。宣言というのは変数で言うならメモリを確保しない名前だけの提示を言います。


例えばグローバルスコープに

int a;

と書けば、aは0で初期化されるのでメモリが確保され、宣言も定義も書いていることに成ります。

関数は

int function();

のように、関数の内容を書かなければ定義にはなりません。これを関数の プロトタイプ宣言と言います。

コンパイラは内容がなくてもプロトタイプ宣言だけ見れば
きっとどこかでまともな関数が定義されているはずだ」と信用してくれます。
だからまだ中身を書いていない内から、あるいはそのコンパイル単位に中身が登場しなくたって、

int a = function();

という風にその関数を使ってもコンパイルが通ります。

ここでコンパイラが責任を持つのは「functionという名前の関数がint型を戻すという事と、aという名前の変数がint型であるので、この式は型の使い方に問題がない」という部分です。

コンパイルはファイル毎に行われので、全体の何処かに本当に定義があるのかどうかに関しては無知なのです。統括の責任を負うのはリンカの役割です。


テンプレート関数のコンパイル


テンプレート関数はコンパイラから見える範囲に定義を書かないとリンクエラーになります。
テンプレート関数は、それを使用するコードを書かない限りは、実際の関数が生成されなかったりします。実際に使われた型のバージョンの関数だけを生成して無駄を省くためです。
cppファイルなどにテンプレート関数の実体を定義し、ヘッダファイルにプロトタイプ宣言してそれをインクルードしてその関数を使った場合、コンパイラはそういう関数があると信じることはできるので、コンパイルは通ります。ここまでは普通の関数と同じです。
しかし普通の関数がcppファイルをコンパイルする際に関数がコンパイルされるのに対し、テンプレート関数はそのファイル内でその関数を利用しない限りは実体化が起こりません。それでリンクの時点でその関数が見つからずにエラーになるらしいです。
その代わりヘッダファイルに定義を書いても大丈夫なのでむしろ便利かも知れません。

クラス宣言とポインタ


どこにも定義していないクラスの名前を次のように書いてもエラーにはなりません。

class MyClass; //MyClassというクラスを使うという宣言
MyClass* myclass; //MyClassのポインタの宣言

class MyClass;
という記述は、どこかにMyClassというクラスを定義しているのだという事をコンパイラに信じさせます。
MyClass* myclass;
は、MyClass型のポインタを使うという宣言であり、メモリを確保しないので実際のMyClassの定義を必要とはしません。

つまり上に書いたclass MyClass;があれば、MyClass型のポインタの宣言が出来るのです。

しかし
MyClass myclass;
と言う風にmyclass変数の定義を書くとMyClassのコンストラクタを呼ぶ必要があるのでMyClass型の実際の定義情報が必要になります。

クラス宣言を使うと、あるクラスの中のメンバとして他のクラス型を持たせる場合、ヘッダファイルに他のクラスのヘッダファイルをインクルードしなくても済みます。

例)
class MyClass;

class tess
{
 MyClass* myclass;
};

この場合、
#include "MyClass.h"は不要です。

0 件のコメント: