【基礎から学ぶプログラミング言語】 C言語/ソースコードを別々のファイルに分割する方法

IT
スポンサーリンク
スポンサーリンク
スポンサーリンク

私たちは日常生活で何気なくパソコンやスマートフォンというコンピュータを使用しています。
これらのコンピュータが普通に動作しているのは、そのようにプログラミング言語が記述されているからです。
本記事は、そんなプログラミング言語について実際に学びながら要点をまとめていったメモという位置付けになります。
私は専攻が電気でプログラムに関しては全くの初心者ですので、同様に初心者の方には理解しやすくなっているかと思います。

今回は、「C言語/ソースコードを別々のファイルに分割する方法」についての説明です。

1.初めに

C言語の勉強を進めるに連れて複雑なプログラムを少しずつ記述できるようになってきましたが、そうすると文字数が結構多くなってきてしまいます。
そうなった最たる理由としては、一つのソースコード内に全てのプログラムを記述していたことが挙げられます。

仮にExcelで資料を作っていたとすると、シートを分けて使いませんか?
データを参照するためだけのシート、結論を簡潔にまとめたシート、詳細を書き記したシート…という具合に、何かしらのルールを自分で決めてシートを分割するということは珍しいことではないかと思います。

それなら、C言語のソースコードもファイルを分割してしまえば良いと思いませんか?
実際、可能です。

ということで、今回はソースコードを別々のファイルに分割する方法について記述していきます。

2.ファイルを分割する方法

プログラムをどのように分割するのかは人それぞれですが、基本は関連するまとまりで分割するのが一般的かと思います。

前回、以下のようなプログラムの動作を説明しました。

#include<stdio.h>

void sample1(int, int);
void sample2();

void main() {
int a = 1 , b = 2;
printf(“mainを処理…\n”);
printf(“ローカル変数 a = %d 、b = %d\n”, a, b);
printf(“main実行完了\n\n”);
sample1(3, 4);
sample2();
}

void sample1 (int a, int b) {
printf(“sample1を処理…\n”);
printf(“ローカル変数 a = %d 、b = %d\n”, a, b);
printf(“sample1実行完了\n\n”);
}

void sample2() {
int a = 5, b = 6;
printf(“sample2を処理…\n”);
printf(“ローカル変数 a = %d 、b = %d\n”, a, b);
printf(“sample2実行完了\n\n”);
}

図1

このようなプログラムの場合、プロトタイプ宣言・main・ユーザー定義関数の3セクションに分けるとわかりやすくなりますので、実際にファイルを分割してみようと思います。

ファイルを分割する前のソリューションエクスプローラー(画面右に表示されている)は以下の通りになっています。

図2

今は全プログラムがソースファイルである「main.c」に入っている状態です。
※.cの拡張子はC言語のプログラムを指しています。
これが最終的には以下のように変化します。

図3

では、実際にファイルを分割してみましょう。

まずは、「main.c」のソースコードの内容を以下のように変更します。

#include<stdio.h>
#include “test.h”

void main() {
int a = 1 , b = 2;
printf(“mainを処理…\n”);
printf(“ローカル変数 a = %d 、b = %d\n”, a, b);
printf(“main実行完了\n\n”);
sample1(3, 4);
sample2();
}

図4

main関数だけ残って、大分スッキリしましたね。

「【#include】はヘッダファイルを読み込む際の宣言で、C言語のヘッダファイルは「.h」という拡張子になっているので、これを読み取るために必要なもの」とかなり前の記事で述べました。
この時は説明を先送りにしていたのですが、やっと説明に入れます。

ここでは、2行目に【#include “test.h”】という記述が追加されていますね?
これが、test.hのヘッダファイルを読み込むという意味になります。
これまで特に使用していなかったヘッダファイルをここでやっと使うことになるのです。

では、最初からずっと書いてある【#include<stdio.h>】は何なのかという話なのですが、これは「stdio.h」というVisual studioでデフォルトで用意されているヘッダファイルを指してます。
こいつを読み出しているから、デフォルトで用意されているprintfなどが動作するのです。
要するに、Visual studioでC言語を使用する場合は必要になるデフォルト設定ファイルとでも思ってくれれば良いです。
そんなデフォルトのヘッダファイル(標準ライブラリ)の場合は不等号<>で囲み、ユーザ定義のヘッダファイルはダブルクオーテーション“”で囲むというルールになっています

ちなみに、「stdio.h」は「スタンダードIO.h」の意味らしいです。

では、ソースコードを読み込む記述はいらないのかと疑問に感じるかもしれませんが、ソースコードに関してはライブラリというものが用意されていて不要になります。
「.libファイル」というものが用意されていて、「.cファイル」に当たるものが既にコンパイルされた状態になっています。
そういうものだと思っておいてください。

次に、ユーザー定義関数用のソースファイルを用意します。
作り方は簡単で、ソリューションエクスプローラーのソースファイルにカーソルを合わせた状態で右クリックして、「追加」の「新しい項目」を選択します。

図5

すると、追加するソースコードの種類選択画面が開きますので、今取り扱っているC言語に対応させて拡張子を「.c」にして名前を付けて決定をします。
設定によってはC言語の選択肢が出てこないので、その場合は自力で「.c」にしましょう。
デフォルト名は「ソース.cpp」になっているので、「ソース.c」にしてあげれば良いわけです。
ここでは「user.c」という名称にしてみました。

図6

そんな「user.c」のソースコードは以下のように入力します。

#include “test.h”

void sample1(int a, int b) {
printf(“sample1を処理…\n”);
printf(“ローカル変数 a = %d 、b = %d\n”, a, b);
printf(“sample1実行完了\n\n”);
}

void sample2() {
int a = 5, b = 6;
printf(“sample2を処理…\n”);
printf(“ローカル変数 a = %d 、b = %d\n”, a, b);
printf(“sample2実行完了\n\n”);
}

図7

先程述べたようにヘッダファイルを読み込む必要があるので、mainの時同様に【#include “test.h”】を追加し、ユーザー定義関数に当たる部分をそのまま移動しただけです。

残るはヘッダファイルだけです。
まずは、どんな感じにすれば良いのかを見てみてください。

#ifndef _TEST_H_
#define _TEST_H_

void sample1(int, int);
void sample2();

#endif

図8

プロトタイプ宣言を持ってくるだけかと思いきや、1行目・2行目・7行目に変なのが追加されてきましたね。

「main.c」と「user.c」のソースコードを思い返して欲しいのですが、これらのソースコードにはヘッダファイルを読み込むために【#include “test.h”】と記述していましたよね?
この記述、2回記述してあります。
するとどうなるかと言うと、ヘッダファイルを2回読み込むのでプロとファイル宣言が2回行われてしまうことになります
このことを二重インクルードと呼びます。

1行目・2行目・7行目の変な記述は、この二重インクルード対策に必要なマクロです。
ここで出てきたマクロは、コンパイラ(プログラミング言語で記述されたプログラムを機械語に翻訳/コンパイルするプログラム)に指令を与えるもののことです。
※頭に「#」が付いているものがマクロです。

意味合いとしては以下のようになっています。

1行目

user.hが定義されていなければ2行目~6行目を実行し、定義されているのならendif(7行目)までスキップする。
おそらく「if」「not」「define(定義する)」の略称なのではないかと思われる。

2行目

user.hを定義する。
[define]は[定義する]という意味。

3行目

ifndefの対となる〆の部分。

つまり、「【#include “test.h”】で1回目に呼び出された際はまだ定義されていないので2~6行目が実行されてプロトタイプ宣言をしますが、もう1回呼び出された際はもう既に定義されているから7行目に飛ぶ」という処理が可能になるのです。

ちなみに、ヘッダファイル名が大文字になっているのは、見やすいように慣習的にそうなっているだけらしいです。
長い物には巻かれろの精神で、【_ヘッダファイル名_H_】の形式にしておけば間違いは無いです。
表記のルールは他にもありますが、基本これだけで充分なのでこれだけ押さえておきましょう。

…と説明しましたが、今のVisual studioなら二重インクルード対策をしていなくても普通に動くようになっています。
ただ、古いソースコードのメンテナンスをする場合は使用しているコンパイラも古くなる為、この対策を実施していないとうまく動作しなくなります。
結局は記述しておけば何の問題もないということです。

これで「test.h」・「user.c」・「main.c」にファイルが3分割されましたが、問題無く実行できるプログラムが出来上がります。

3.グローバル変数をファイル分割した場合の注意点

ファイルを分割したとしてもグローバル変数はどこのソースファイルに記述しても問題は無いです。
「A.c」と「B.c」というソースファイルを用意したとすると、どちらのソースファイル内に定義しても良いのです。

ただ、注意点が1つあります。

仮に「A.c」でグローバル変数を定義して使用したとします。
ここでは【int z;】と定義したとします。
そして、「B.c」内でも同じグローバル変数zを使用したかったとします。
この場合、どうすれば良いと思いますか?

「A.c」内で一度定義しているので、もう一度同じグローバル変数zを定義することはできません。
ですが、「B.c」内に定義せずにグローバル変数zを登場させても、他のソースファイル内で定義されているグローバル変数zをそのままでは読み取ることができないんです。
グローバル変数は同ソースファイル内ならどこに記述されていても同じ変数として扱えるものであって、ソースファイル違いまではサポートしていないのです

なので、もしそんな事例があったら、「B.c」内で【extern int z;】と定義してあげてください。
グローバル変数の前に「extern」と付けるのです。
こうすることで、「このグローバル変数は他のソースファイル内で定義されているよ!」という意味合いになります。
こうして紐付ける処理が必要になるということは覚えておきましょう。

ちなみに、「extern」は今開いているソースファイルとは別のソースファイルで定義されていると宣言しているだけなので、ここで“=”をつけて【extern int z = 0;】のように記述することはできません。
あくまで定義しているのは外のソースファイル側なので、初期化したいならそちらで行いましょう。

4.ファイルを分割する他の利点

ファイルの分割はソースコードを見やすいものにするという以外にも利点があります。
それは役割分担ができるようになるという点です。

大規模なプロジェクトがあったとしたら、普通は役割分担をして望みますよね。
営業・調達・設計など、人それぞれ任される仕事が変わります。

ソースコードにおいても同じことが言えるのです。

機能単位で分けて考えて、「○○に関するソースコードはAさん」・「△△に関するソースコードはBさん」という具合に役割を分担して同時に開発を進めます。
そんな時に、一つにまとまったソースコードを全員で弄りまわすのは現実的ではないでしょう?
だって、誰かが編集していたら他の人は編集できないんですから。

そこで、担当者が各々持ってきたソースコードを後で掛け合わせるのです。
このように、分割されたファイル(別々の人が作ったファイル)をかき集めて一つの大きなプログラムを作り上げるということはよくあります。

なので、ファイルを分割するという考えは結構大事なんですね。

以上、C言語/ソースコードを別々のファイルに分割する方法についての説明でした。