見出し画像

C言語 「main」関数と分離する

序章

前回までで足し算プログラムを作りました。

関数は「main」関数が1つだけ。

「main」関数はC言語において特殊です。
プログラムを実行したとき、この「main」関数から実行されます。
プログラムの中で「main」関数は必ず必要であり、複数存在してはいけません。唯一無二の存在です。

C言語で書かれたプログラムは必ず「main」関数を持ちます。
プログラムAからプログラムBへコードを移植するときに、「main」関数に書いていたのでは面倒になります。「main」関数と切り離していれば、移植もしやすくなるでしょう。

「main」関数は、関数を1つだけ呼び出す。

それくらいで丁度いい。

前回の足し算プログラムも「main」関数と切り離してみます。


「main」関数の分離

「main」関数にあった「return」以外のコードを全て関数「add_stdio」に移動します。
「main」関数は「add_stdio」を呼び出すだけになります。

 #include  <stdio.h>
void add_stdio();

int main()
{
        add_stdio();
        return 0;
}

void add_stdio()
{
        for (;;)
        {
                int a;
                int b;
                int x;
                int n;
                printf("number1? --->");
                n = scanf("%d", &a);
                if (n != 1)
                {
                        break;
                }

                printf("number2? --->");
                n = scanf("%d", &b);
                if (n != 1)
                {
                        break;
                }

                x = a + b;
                printf("%d + %d = %d\n", a, b, x);
        }
}

ここで1行増えています。

これは「プロトタイプ宣言」と言います。
「add_stdio」の引数の数、引数の型、戻り値の型を宣言します。

「add_stdio」はこの時点では実態がまだないので、「main」関数が呼び出す時にコーリングシーケンスがわかりません。このため事前に宣言しておきます。「main」関数で「add_stdio」を呼び出す時に、引数、戻り値に間違いがないかどうかをコンパイラがチェックしてくれます。

「add_stdio」は、引数なし、戻り値なしのシンプルな関数ですが、「main」関数で要らぬ引数を付け足すとコンパイルエラーになります。

例えばこんな風に、「add_stdio」の引数に「1」を追加すると・・・。

int main()
{
        add_stdio(1);
        return 0;
}

こんなエラーになります。
(正確には“error”ではなく“warning”)

add.c:6:11: warning: passing arguments to 'add_stdio' without a prototype is deprecated in all versions of C and is not supported in C2x [-Wdeprecated-non-prototype]
        add_stdio(1);
                 ^
1 warning generated.

翻訳すると。

プロトタイプなしで 'add_stdio' に引数を渡すことは、C のすべてのバージョンで推奨されておらず、C2x ではサポートされていません。

プロトタイプの宣言はあるので、「プロトタイプなし」という警告には少々違和感がありますが、コンパイルエラーのメッセージというのはしばしば違和感があるものです。

とにもかくにも、関数「add_stdio」を分離しました。
ついでに、ソースファイルも分離しましょう。


ソースファイルの分離

ファイル「main.c」に「main」関数を、
ファイル「add_stdio.c」に「add_stdio」関数を、
それぞれ格納します。

main.c

void add_stdio();
int main()
{
        add_stdio();
        return 0;
}

add_stdio.c

 #include  <stdio.h>
void add_stdio()
{
        for (;;)
        {
                int a;
                int b;
                int x;
                int n;
                printf("number1? --->");
                n = scanf("%d", &a);
                if (n != 1)
                {
                        break;
                }

                printf("number2? --->");
                n = scanf("%d", &b);
                if (n != 1)
                {
                        break;
                }

                x = a + b;
                printf("%d + %d = %d\n", a, b, x);
        }
}

解説

「main.c」は、もう「<stdio.h>」をincludeする必要はありません。標準入出力を使いませんので。

さて。

これでもいいんだけど。

ここね。

main.c

「add_stdio」のプロトタイプ宣言だけど。
ここに書くのはあまりよくないですよね。
他のファイルで「add_stdio」を使う度に宣言しなければなりません。
「add_stdio.c」が提供する関数がもっと増えれば、プロトタイプ宣言をどんどん増やさなければならないのかということもあります。

ですからヘッダファイルで提供しましょう。


ヘッダファイルの作成

「add_stdio.c」が、外部に提供する関数・マクロをヘッダファイルで定義します。

add_stdio.h

 #ifndef  ADD_STDIO #define  ADD_STDIO

void add_stdio(void);
 #endif 

他にマクロもないのでヘッダファイルに記載するのは、関数「add_stdio」のプロトタイプ宣言だけです。

#ifndef ADD_STDIO
#define ADD_STDIO

#endif

これは二重インクルードを抑圧するための決まり文句です。

 #include  add_stdio.h
 #include  add_stdio.h

のように、2回インクルードされた場合、二重定義のエラーが発生することがあります。決まり文句を書いておけばこれを抑止することができます。

ヘッダファイルでプロトタイプを宣言したら、「main.c」と「add_stdio.c」の両方でこのヘッダファイルをインクルードします。

main.c

 #include  "add_stdio.h"

int main()
{
        add_stdio();
        return 0;
}

add_stdio.c

 #include  "add_stdio.h" #include  <stdio.h>

void add_stdio()
{
        for (;;)
        {

考察

「main.c」をコンパイルするとき
「add_stdio.h」のプロトタイプ宣言と比較されます。

「add_stdio.c」をコンパイルするとき、この時にも、やはり「add_stdio.h」のプロトタイプ宣言と比較されます。

その結果、
「main.c」の呼び出す方と、
「add_stdio.c」の呼び出される方で、
コーリングシーケンスが一致することが点検されることになります。

これは当たり前のことではあるのですが、大昔のCコンパイラではコーリングシーケンスをチェックしてくれませんでした。「数百の関数を間違えずに呼び出す」などということは到底難しく、引数の数を間違えようものならプログラムはすぐに暴走します。どこで暴走したのか捕まえるのも至難の技。

この類いの問題をコンパイラが解決してくれるということがどれほど有難いことか。

ただ。

C言語の場合、プロトタイプ宣言を必要とするということがいささかの難点ではあります。コーリングシーケンスを常に二度書きしなければなりません。煩わしいことこの上なし。C++でなくなるかと思ったけど、なくならなかったなぁ。

後記

さて。

取り敢えずは「main」関数から切り離しました。
次はもう一歩進めてみましょう。

(つづく)


この記事が気に入ったらサポートをしてみませんか?