• 検索結果がありません。

数はファイル内のどの関数からでも参照できるので便利ではありますが 変数の衝突が起こったり ファイル内のどこで値が書き換えられたかわかりづらくなったりなどの欠点があります 複数の関数で変数を共有する時は出来るだけ引数を使うようにし グローバル変数は プログラムの全体の状態を表すものなど最低限のものに留

N/A
N/A
Protected

Academic year: 2021

シェア "数はファイル内のどの関数からでも参照できるので便利ではありますが 変数の衝突が起こったり ファイル内のどこで値が書き換えられたかわかりづらくなったりなどの欠点があります 複数の関数で変数を共有する時は出来るだけ引数を使うようにし グローバル変数は プログラムの全体の状態を表すものなど最低限のものに留"

Copied!
7
0
0

読み込み中.... (全文を見る)

全文

(1)

第10章 分割コンパイル

§1 ソースを分割する

今まで出てきたソースは全て一つのソースファイルにソースを記述してきました。しか し、ソースが長くなっていくと全てを一つのファイルに書くと読みづらくなります。そこ で、ソースを複数のファイルに分割してコンパイルを行う分割コンパイルをします。今章 は章名にもなっている、分割コンパイルの方法についてやります。 分割コンパイルする時は大抵、関連性のある機能ごとにファイルにまとめます。そうす ることによって、ソースが長くなった時に複数のファイルに分け、更に機能ごとにまとま るので修正する時など読みやすくなります。良く使う処理を別のファイルにしていろんな プログラムで使いまわすということも考えられます。また、変更を加えたファイルだけコ ンパイルすることが出来るので、コンパイルの時間を減らすことが出来ます。更に、現在 プログラムが作られる時は大抵複数人で行います。そういう時ももちろん分割コンパイル します。 分割コンパイルは次のセクションで述べる変数のスコープ(有効範囲)をしっかり把握 していれば難しいものではありません。

§2 変数のスコープ

変数はどこで宣言するかによってどこで使えてどこで使えないかが決まります。例えば 今まで関数内で宣言した変数は関数内でしか使えませんでした。これに対し、関数の外で 宣言するとそのファイル内全体で使用することができます。関数内でしか使えないような 変数をローカル変数、ファイル内全体で使用することが出来る変数をグローバル変数と呼 びます。 変数のスコープ

上のようなソースだった場合、a は main 関数でも func 関数でも使うことが出来ます。 それに対し、b は main 関数のみ、c は func 関数のみで使うことが出来ます。グローバル変 int a; int main(void){ int b; … } void func(void){ int c; … }

(2)

数はファイル内のどの関数からでも参照できるので便利ではありますが、変数の衝突が起 こったり、ファイル内のどこで値が書き換えられたかわかりづらくなったりなどの欠点が あります。複数の関数で変数を共有する時は出来るだけ引数を使うようにし、グローバル 変数は、プログラムの全体の状態を表すものなど最低限のものに留めておきましょう。(最 初はよく使うことになるでしょうが…。) 先ほどグローバル変数の説明にファイル内全体で使えると書きました。今章ではソース の分割を行いますが、複数のファイルで変数を共有する時も出てきます。その場合はどう するかというと、その変数を共有したいファイル内でextern 宣言と呼ばれる宣言をします。 複数のファイルで変数を共有する

main 関数内や func1 関数内では当然変数 a は使うことが出来ます。func2 では宣言がさ れていないのでa は使うことは出来ません。func3 ではファイル 3 内で extern 宣言をして いるのでファイル3 内では普通にグローバル変数として用いることが出来ます。int a;と書 くとa という変数のためにメモリ領域を取るなどが行われます。extern int a;と書くと他の ファイルにある a という変数をこのファイル内で用いるということを宣言しています。関 数のプロトタイプ宣言も本来は頭にextern をつけるのを省略しているだけで、意味合いは 同じです。 ○ファイル1 int a; int main(void){ … } void func1(void){ … } ○ファイル3 extern int a; void func3(void){ … } ○ファイル2 void func2(void){ … }

(3)

§3 Makefile

それでは、具体的に分割コンパイルをする方法を見てみましょう。分割コンパイルを行 うにはMake と呼ばれる作業が必要です。Make を行うには何通りか方法があります。まず は、全てを手作業で行う方法です。これについては後ほど触れます。他にMakefile と呼ば れるものを使う方法があります。Visual C++などの統合環境を用いると勝手に作ってくれ るのですが、まずは自分で書いてみましょう。それでは例を示します。 Makefile を使った分割コンパイルの方法(プログラム 10.1−1) まずはコンパイルするソースを作ります。今までと違って今回はファイル名を指定して いますが、別の名前でもかまいません。(その場合は次に示す Makefile の所でファイル名 を用いますので各自ファイル名を置き換えてください。) main 関数内で func 関数を呼び出すので関数のプロトタイプ宣言を行います。これはフ ァイルを分割してもしなくても同じことです。 また、それぞれのファイルでstdio.h をインクルードしています。ヘッダーファイルをイ ンクルードする時はそれぞれのファイル内で使う関数を使うのに必要なヘッダーファイル をそれぞれインクルードします。(この例だとmain 関数では必要ないですが…。)分割した ファイルのどこかでインクルードしたら別のファイルでも使えるということは無いので注 意してください。 それでは次にMakefile の作り方を示します。ファイル名は「makefile」にしておいて下 さい。 ○ファイル名:main.c #include <stdio.h> void func(void); int main(void){ func(); return(0); } ○ファイル名:func.c #include <stdio.h> void func(void){ printf("関数が呼び出されました。"); }

(4)

Makefile を使った分割コンパイルの方法(プログラム 10.1−2) #から始まる行はコメント文を表します。target は CPad に実行ファイルがどれかを知ら せるための文でMake には関係ありません。 all はシンボリックターゲットと言って、その後ろにあるファイル名が最終的に作られる ファイルです。二つ以上の実行ファイルを作る時はその数だけ実行ファイル名を書きます。 その下からは実際にMake する時のファイルの依存関係と Make するためのコマンドです。 依存関係は必ず行頭から書き、コマンドは行頭に必ずタブ文字を入れてから書きます。 スペースを入れても動きません。 ところで「.obj」という拡張子があります。これはオブジェクトファイルと言うものです。 今まで特に触れませんでしたが、C のソースを実行形式にする場合二つの手順が踏まれてい ます。コンパイラがソースをコンパイルした物がオブジェクトファイルです。ソース内で 使われているライブラリ関数を使用するのに必要なものと先ほどのオブジェクトファイル をつなげて実行形式にするのがリンクと言う作業です。これが今までコンパイルと呼んで いた作業です。

「main.exe: main.obj func.obj」は main.exe が main.obj と func.obj から出来ているとい うファイル同士の依存関係を示します。「bcc32 -emain.exe main.obj func.obj」はコマンド です。今までCPad を使っていて意識していませんでしたが、本来 bcc はコマンドラインで 使うコンパイラです。このコマンドは実際に Make する時のコマンドと同じです。 -emain.exe はコンパイルする時のオプションで main.exe という名前の実行ファイルを生 成するという意味になります。この時-e と main.exe の間にスペースを入れると動作しませ ん。残りの二つは引数で、main.obj と func.obj を用いると言うことを意味します。 残りの文も基本的には同じ意味です。-c は C のソースからオブジェクトファイルを生成 するためのオプションです。ちなみに、このMakefile は BCC を用いた場合の物で他のコ ンパイラを使うと少し書き方が変わります。

# makefile for main # target : main.exe all : main.exe

main.exe: main.obj func.obj

bcc32 -emain.exe main.obj func.obj main.obj: main.c

bcc32 -c main.c func.obj: func.c

(5)

先ほどMake は全て手作業で行えるといいましたが、Makefile に書いたコマンドを実際 にコマンドラインで実行するとMake することが出来ます。今回の場合は「bcc32 -c main.c」 と「bcc32 -c func.c」を実行した後に、「bcc32 -emain.exe main.obj func.obj」を実行する とMakefile を用いた時と同じ実行ファイルが出来ます。

§4 自作ヘッダーファイル

ヘッダーファイルは自分で作ることが出来ます。ヘッダーファイルは関数や変数の宣言 のみが記載されたもので、ファイルの分割を行う際に用います。先ほどの例だとmain.c の void func(void);の宣言は普通ヘッダーファイルに書きます。それではヘッダーファイルを 用いた場合の分割例とMakefile の書き方を示します。 Makefile を使った分割コンパイルの方法 2(プログラム 10.2−1) 先ほどの例と違う点はmain.c で読み込むヘッダーファイルが増えている点と、関数の宣 言がヘッダーファイルに移っただけです。ヘッダーファイルをダブルクォーテーションで 囲んでいますがこれは自作ヘッダーファイルをインクルードする時は普通ダブルクォーテ ーションで囲みます。これは、コンパイラが<>で囲んだ場合は標準ライブラリから探し “”で囲んだ場合は同じフォルダないから探すといった動作をするからです。また、見て すぐに自作なのか標準ライブラリにあるのかわかりやすくする意味もあります。ヘッダー ファイルはこのように関数の宣言をそのまま書いたものです。しかし、普通は関数の宣言 が2 重にならないようにプリプロセッサを用いて条件コンパイルさせます。 ○ファイル名:main.c #include <stdio.h> #include “func.h” int main(void){ func(); return(0); } ○ファイル名:func.c #include <stdio.h> void func(void){ printf("関数が呼び出されました。"); } ○ファイル名:func.h void func(void);

(6)

多重インクルードを防ぐヘッダーの書き方(プログラム10.2−2) 一見意味のないように見えるかもしれませんが、こうする事によって同じヘッダーファ イルを分割コンパイルする複数のファイル内で宣言されても関数の宣言が 2 重になりませ ん。stdio.h 等最初から用意されているヘッダーを見てみれば似たような記述があるはずで す。条件コンパイルを忘れているかもしれませんので軽く復習すると意味は 1 行目が 「_func」がマクロ定義されていなければコンパイルすると言う意味で、2 行目は普通のマ クロの定義です。最後の行は条件分岐の最後につけるものでした。これで、このヘッダー ファイルが呼び出されたのが2 回目であれば既に「_func」がマクロ定義されていてインク ルードされません。 ところで、#endif の後ろにコメントがありますが、マクロは大抵入れ子になってもイン デントしないので、どれと対応するのかがわからなくなるためこのように書くことがあり ます。このような短いヘッダーファイルならあまり意味はありませんが、長くなった時に は必須とも言えます。 それでは次にMakefile の書き方を見てみましょう。 Makefile を使った分割コンパイルの方法(プログラム 10.2−3) 先ほどと違うのは、main.obj のファイルの依存関係に func.h が追加されているだけです。 これは書かなくても動きますが、普通はファイルの依存関係を示すために書きます。 ○ファイル名:func.h #ifndef _func #define _func void func(void); #endif //_func

# makefile for main # target : main.exe all : main.exe

main.exe: main.obj func.obj

bcc32 -emain.exe main.obj func.obj main.obj: main.c func.h

bcc32 -c main.c func.obj: func.c

(7)

§5 統合開発環境

分割コンパイルを行う際にMakefile を自動的に作ってくれるソフトがあります。統合環 境には有名なもので VisualC++があります。他にも今まで使ってきたコンパイラを使う BCC Developer があります。別途使い方を示しますので今回のサンプルプログラムをコン パイルして使い方を確かめてください。

§6 10 章のまとめ

大きなプログラムを組む時に分割コンパイルは必須になります。今のうちにある程度な れていた方がいいかもしれません。また、変数のスコープの考え方は大変大事な物なので 忘れないようにして下さい。 確認問題 分割コンパイルを用いたプログラムを作りなさい。(Makefile、統合環境両方でやる事。)

余談とおまけ

分割コンパイルとヘッダーファイルを使うと、実際のソースを隠す事が出来ます。実際 標準ライブラリ関数はオブジェクトファイルの状態で提供されています。その時に関数の 仕様を表すのがヘッダーファイルです。ヘッダーファイルはソースを読む時の手がかりに もなるのでそういう事を意識して書くようにしましょう。 また分割コンパイルがなくても出来ますが、関数ラッパという手法があります。これは、 既にある標準ライブラリ関数に引数を渡す前に自分で処理を挟む事が出来ます。標準ライ ブラリ関数は渡す引数によっては問題を起こす時もあるので引数のチェックをするべきで すが、それを関数として作ってライブラリにしておくと、再利用する事が出来て便利です。 関数ラッパの詳しい事については自分で調べて見てください。

参照

関連したドキュメント

断するだけではなく︑遺言者の真意を探求すべきものであ

としても極少数である︒そしてこのような区分は困難で相対的かつ不明確な区分となりがちである︒したがってその

いてもらう権利﹂に関するものである︒また︑多数意見は本件の争点を歪曲した︒というのは︑第一に︑多数意見は

核種分析等によりデータの蓄積を行うが、 HP5-1

夜真っ暗な中、電気をつけて夜遅くまで かけて片付けた。その時思ったのが、全 体的にボランティアの数がこの震災の規

自分ではおかしいと思って も、「自分の体は汚れてい るのではないか」「ひどい ことを周りの人にしたので

これも、行政にしかできないようなことではあるかと思うのですが、公共インフラに