こんにちは、ナナです。
C言語のプログラムは「ソースコード(ソースファイルとも呼ぶ)」と「ヘッダファイル」の2種類で構成されます。
それぞれのファイルには「どこに何を書くべきか?」というのが暗黙的に決まっており、このルールを知らないと予期せぬビルドエラーに悩まされることになります。
本記事ではソースコードには、いったい何を書くべきかに焦点を当てて解説します。
ファイルの構成について知りたい方は『C言語 ファイル分割の考え方【何を基準に分けるのかを解説】』のまとめ記事を読むとよいでしょう。
本記事では次の疑問点を解消する内容となっています。
では、ソースコードの書き方を学んでいきましょう。
ソースコードに書かれるべき部品とは
師匠!ヘッダファイルに引き続き、ソースコードにはいったい何を書くべきなんですか?
やっぱりヘッダファイルで書かなかったものを書くってことで合ってますよね?
そうだね。ヘッダファイルに書かないものを、ソースコードに書くのは基本的な考え方だね。
ただし、いくつかの項目はヘッダファイルにもソースコードにも書く場合もあるんだよ。
ソースコードにはいったい何をどこに書くべきなのかを示しましょう。
ソースコードの構成部品
ソースコードは次の構成部品で定義します。
いくつかの部品はヘッダファイルにも登場したものが含まれていますね。
ヘッダファイルについて詳細を知りたい方は『C言語 ヘッダファイルの書き方【サンプルフォーマットを公開】』の記事を参考にしてください。
部品を配置してよいかの判断リスト
次のリストは、C言語における主な部品一覧です。ソースコードに書くものは『〇』のものです。
部品 | 記入可否 | 役割 |
---|---|---|
多重インクルード防止 | × | ヘッダファイルを多重にインクルードされるのを防止 |
ヘッダファイルインクルード | 〇 | #includeによる外部ファイルのインクルード |
マクロ定義 | △ | #defineによる定数定義 |
型定義 | △ | 構造体、列挙型、共用体のデータ型定義 |
プロトタイプ宣言 | △ | 関数のプロトタイプ宣言 |
グローバル変数外部参照宣言 | × | 外部ファイルへ公開するグローバル変数の参照宣言 |
グローバル変数定義 | 〇 | グローバル変数の実体定義 |
関数定義 | 〇 | 関数の実体定義 |
※「△」の項目はソースコードに記述するケースもある部品です。
「変数定義」と「関数定義」がソースコードの主役となる部品ですね。この2つは、今までたくさん作ってきたので大丈夫ですよね!
ソースコードの定義例と書き順について
師匠!ソースコードにも「お手本の書」はもちろんありますよね?あるんでしょ?それを見せてくださいっ!
あ、ありますよ…。しょうがない、大盤振る舞いで見せちゃいましょっ!
具体的なソースコードのプログラム例を示しましょう。
ソースコードのサンプル定義例
このソースコードの定義例は、私が実際に開発するときの基本フォーマットです。
main.c
#include <stdio.h>
#include <string.h>
#include "sub.h"
//------------------------------------------------
// 非公開マクロ定義
//------------------------------------------------
#define D_SUB_ID (50)
//------------------------------------------------
// グローバル変数定義
//------------------------------------------------
static long gSystemflg = 0;
//------------------------------------------------
// 概 要:サブ情報のプリント出力
// 引 数:info サブ情報
// 戻り値:正常/異常
//------------------------------------------------
int subPrint(S_SUBINFO info)
{
printf("%s %d", info.moji, info.num1);
return 0;
}
//------------------------------------------------
// 概 要:システムのエントリーポイント
// 戻り値:正常/異常
//------------------------------------------------
int main(void)
{
S_SUBINFO info;
gSystemflg = 1;
info.num1 = D_SUB_ID;
strcpy_s(info.moji, D_SUB_NUM, "Hello");
subPrint(info);
return 0;
}
プログラム内容自体に意味はありませんので、解析する必要はありません。各部品がどのような書き方でどの位置に配置されるかに着目してみてください。
各部品の配置順について
各部品は次の順序で配置しましょう。
関数定義の中でグローバル変数を参照するため、グローバル変数の定義を先に書きます。
グローバル変数の定義はstaticを付けておくとより安全性が高まります。staticについて知りたい方は『C言語 staticの利用価値【システムを安全にする仕組みを解説】』を参考に。
関数ヘッダのコメント
関数定義の直前に記述してあるコメントは、「関数ヘッダ」と呼ばれるコメントです。
このように関数の概要や引数・戻り値に関する情報をコメントとして記載しておくことで、第3者がプログラムを読んでも理解しやすいように補足しています。
//------------------------------------------------
// 概 要:サブ情報のプリント出力
// 引 数:info サブ情報
// 戻り値:正常/異常
//------------------------------------------------
int subPrint(S_SUBINFO info)
実際の開発では関数が数百、数千といった規模になることも珍しくありません。このようにコメントを残すことで可読性やメンテナンス性をよくしていきます。
プログラムは常に第3者が読むことを意識することです。
このようなコメントに対し「自分だけが読むプログラムだからそんなの不要だよ」と思うかもしれませんが、1か月後の自分は第3者になっていますよ!
ソースコードにグローバル変数と関数定義は置きましょう
師匠!なんでグローバル変数はヘッダファイルじゃなくて、ソースコードに定義するんですか?
ダメって言われると書きたくなる性分なんですっ!ヘッダファイルに少しだけ書いてもいいですよね?
ダメです。絶対ダメ!ビルドエラーが起きて困るのは自分ですよー。ちゃんと理由があるので、自分でダメな理由を納得してください。
ここまで関数定義に関しては、ソースコードに書いてきたため迷わないと思います。
しかし、グローバル変数の定義となると「どこに書けばいいの?」とわからなくなる方がいます。
この時についやってしまうのが、「ヘッダファイルにグローバル変数を定義」してしまうことです。
グローバル変数の定義はヘッダファイルに書いてはなりません。
これには明確に書いてはならない理由があるのです。それでは理由を説明していきましょう!
グローバル変数をヘッダファイル書くといったい何が起きるのか?
もしも、グローバル変数の定義をヘッダファイルに記述するとどうなるのかを見てみましょう。
とあるヘッダファイルが、複数のソースコードからインクルードされることは普通のことです。
ヘッダファイルにグローバル変数の定義があると、システム全体で同一名称のグローバル変数がいくつも定義されてしまい「多重定義」となる問題が発生します。
このルールは関数定義にも当てはまります。関数定義はヘッダファイルに書いてはならないのです。
この問題は多重インクルード防止では対策できません。勘違いしないようにしましょう。
「関数定義」と「変数定義」はソースコードに必ず書くことです!ヘッダファイルに書いてはならないことを覚えておきましょう。
ソースコードに「マクロ定義」「型定義」「プロトタイプ宣言」を書くケースとは
師匠!「マクロ定義」「型定義」「プロトタイプ宣言」ってヘッダファイルに書く部品ですよね。ソースコードにも書くことがあるってどんな時なんですか?
ヘッダファイルの役割は「外部へ公開したい情報」を記載するものなんです。つまり、外部に公開したくない場合はソースファイルに書きます。
これらの要素を、「ヘッダファイル」「ソースコード」のどちらに書くかという判断は『外部に対して公開すべき情報なのか』という観点が必要となります。
まずは、「機能を外部公開する」という観点を手に入れましょう。
関数サービスを「外部のソースコードへ提供する」という捉え方
C言語に限らずですが、プログラムというのは「機能」という単位を、1つのソースコードで管理するのが一般的です。
次の図は「機能A」「機能B」が「機能C」のサービスを利用している様子を示しています。
つまり、「機能C」は外部の「機能A」と「機能B」へサービスを提供しているとも言えます。
プログラムで、このサービス提供を実現するために「機能A」と「機能B」では、「機能C」が定義する関数プロトタイプ宣言が最低限必要となります。
これを実現するのが、ヘッダファイルによるサービス情報のインクルードなのです。
とある機能を使いたいと思ったら、対象機能のヘッダファイルをインクルードする。これがシステムを構築する基本構造です。
関数の引数に構造体が必要なケースでは、構造体の型定義も必要となるでしょう。このように外部からサービスを使うということは、サービスを使う側でも様々な情報が必要となるのです。
それを実現するのが、ヘッダファイルのインクルードなんです。
外部に提供したくない情報はソースコードで定義せよ
ヘッダファイルの役割とは、外部からサービスを利用するための情報提供です。
逆に言うと、
外部に公開する必要のない情報をヘッダファイルに書いてはなりません。情報というのは可能な限り、小さく閉じておくのが良いのです。
具体的にプログラムで「外部公開」「外部非公開(内部)」の例を示しましょう。
「D_NUM」というマクロ定義は、sub.cのソースコード内でしか使用しない定義です。そのため、ソースコードで定義を行います。
結論ですが、
ソースコード内に「マクロ定義」「型定義」「プロトタイプ宣言」を記述するケースは、対象ソースコード内でしか使用しない情報を定義する場合です。
外部公開しない情報はソースコード内で情報を閉じる。このようにすることで、システムのメンテナンス性が向上します。