第41章 私はαでありωである

 ようやく長かったアルゴリズム編も終わり、また言語の話に戻ります。

 今回は今まで殆ど語られなかった main 関数についての話をしようと思います。なぜ戻り値が int なのか、なぜ 0 を返していたのか、そのあたりを話していこうと思います。


 では、今回の要点です。


 では、いってみましょう。


 main 関数についての話をしたのは第1部第2章同第7章です。これらの章で話したことをまとめると、

の2つです。

 適当な場所からプログラムを始められるようにすると、どこからプログラムが実行されるのかが分かりにくくなります。そこで、main という関数を特別に扱って、その関数を最初に実行しようという風にしているわけです(厳密には、main より前に外部変数の初期化処理が実行されるので main が必ず最初に実行されるわけではありませんが、その話は例外ということで)。

 ここまでの話は特に問題はありません。しかし、「main の中から始まるんは分かったんやけど、何で main に戻り値があるん?」という疑問を持った方も多いことでしょう。main を直接呼ぶことはないのだから、戻り値なんてなくても構わないと思いませんか?

 しかし、何とこの戻り値を使うことがあるのです。main を直接呼ぶことはなしに、です。

 main が終わるとプログラムが終わるので、main はプログラムの始まりでもあり、同時にプログラムの終わりでもあるわけです。ということは、その main の返す戻り値はどうなるでしょうか?

 プログラムが終わった後は、プログラムを呼んだ「何か」に処理が戻ります。main の戻り値はその「何か」に渡されます。それはOSであったり、別のプログラムであったりします。MS−DOSではバッチファイル、UNIXではシェルであることもあります。とにかく、main の戻り値はその「何か」に渡されます。

 main の戻り値はプログラムの終了コードと呼ばれます。どういう状態でプログラムが終わったかを表すわけです。基本的には0が正常終了で、その他が異常終了です。stdlib.h(Standard Library の略)というヘッダファイルには終了コード用のマクロが用意されていて、EXIT_SUCCESS が正常終了で、EXIT_FAILURE が異常終了です。

 この話をしていなかったので、今までは混乱を避けるためにどんな状態でも0を返していました。ですが、これからは異常終了の場合には EXIT_FAILURE など、別の値を返すことにします。

 この終了コードを受け取った「何か」は、終了コードをもとに処理を分岐することができます。プログラムがどういう状態で終わったかによって処理を分岐することができるわけですね。このために、main は戻り値を持つわけです。

 void main にしてもエラーの出ない環境もありますが、その場合戻り値は不定です。初期化していない変数にどんな値が入っているのか分からないのと同じような状態です。場合によっては不便なこともあるので、void main は使わない、いや、使ってはいけないのが常套です。はい。

 また、戻り値の型も return 文も書かなくてよい環境もあるでしょう。その場合は勝手に0が返されるようになっているはずです。そうなっていないようでしたら、おとなしく return 文を書きましょう。戻り値の型は、実は省略すると勝手に int になるので、戻り値の型は書かなくても大丈夫でしょう。でも、なるべく戻り値の型は全部きちんと書くことを奨めます。また、クラス内ではコンストラクタと勘違いされるので戻り値の型を省略することはできません。


 それでは、実際に main の戻り値を使ってみましょう...と言いたいところですが、使い方はもろに環境に依存します。VC++での例を一応示しますが、これが全ての環境で当てはまるわけではない点に注意して下さい。

 先ず、1つプログラムを作ります。

プログラム1
// Main1.cpp
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char buffer[1024];

    cout << "九九表をファイルに出力します。" << endl
         << "出力ファイル名を入力してください > " << flush;
    cin >> buffer;

    // $ を入力すると、終了します
    if(strcmp(buffer, "$") != 0)
        return EXIT_FAILURE;

    // ファイルの存在確認です
    // ファイルがあれば異常終了します
    // つまり、上書きしないようにしたいわけです
    FILE* file = fopen(buffer, "r");
    if(file != NULL)
    {
        fclose(file);
        return EXIT_FAILURE;
    }

    // ファイルを開きます
    // 今度は開けなかった場合に異常終了します
    file = fopen(buffer, "w");
    if(file == NULL)
        return EXIT_FAILURE;

    // 九九表を書き込みます
    for(int i = 1; i <= 9; i++)
    {
        for(int j = 1; j <= 9; j++)
            if(fprintf(file, " %2d", i * j) < 0)
                return EXIT_FAILURE;
        if(fputc('\n', file) == EOF)
            return EXIT_FAILURE;
    }
    fclose(file);

    return EXIT_SUCCESS;
}

 ファイル名を入力して、九九表を出力するプログラムです。今までのプログラムと違うところは、九九表を作れなかった場合に EXIT_FAILURE を返しているところでしょう。これで、EXIT_SUCCESS(0)が返されたときにしか九九表が完成されていないと判断できますね。

 では、このプログラムの名前を kuku.exe とでもしましょう。この名前は好きにしてもらって構いません。このファイルは、環境によって置く場所は違いますが、VC++ではソースファイルと同じ場所に置いて下さい。

 そして、次にもう1つプログラムを作ります。

プログラム2
// Main2.cpp
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>

typedef int kuku_t[9][9];

// 九九表の読み出し
bool KuKu(FILE* file, kuku_t& kuku);
// 表示
bool Disp(kuku_t& kuku);

int main()
{
    char buffer[1024];

    if(_spawnl(_P_WAIT, "kuku.exe",
                        "kuku.exe", NULL) != EXIT_SUCCESS)
    {
        cout << "九九表が正しく作られませんでした。" << endl;
        return EXIT_FAILURE;
    }

    cout << "作成した九九表のファイル名を入力して下さい > " << flush;
    cin >> buffer;

    FILE* file = fopen(buffer, "r");
    if(file == NULL)
    {
        cout << "ファイルが開けません。" << endl;
        return EXIT_FAILURE;
    }

    kuku_t kuku;
    if(!KuKu(file, kuku))
    {
        cout << "ファイルエラー。" << endl;
        return EXIT_FAILURE;
    }

    while(Disp(kuku));

    return EXIT_SUCCESS;
}

// 九九表の読み出し
bool KuKu(FILE* file, kuku_t& kuku)
{
    for(int i = 0; i < 9; i++)
        for(int j = 0; j < 9; j++)
        {
            int fRet = fscanf(file, "%d", &kuku[i][j]);
            if(fRet == 0 || fRet == EOF)
                return false;
        }

    return true;
}

// 表示
bool Disp(kuku_t& kuku)
{
    int i, j;

    cout << "どこを表示しますか > " << flush;
    cin >> i >> j;

    if(i < 1 || 9 < i ||
       j < 1 || 9 < j)
        return false;

    cout << kuku[i - 1][j - 1] << " です。" << endl;
    return true;
}
実行結果例
九九表をファイルに出力します。
出力ファイル名を入力してください > $
九九表が正しく作られませんでした。
実行結果例2
九九表をファイルに出力します。
出力ファイル名を入力してください > kuku.txt
作成した九九表のファイル名を入力して下さい > kuku.txt
どこを表示しますか > 9 9
81 です。
どこを表示しますか > 3 7
21 です。
どこを表示しますか > 0 0

 先頭にアンダーラインのついた、環境依存バリバリの関数 _spawnl を使いました。これを使うとプログラムを実行できます。戻り値は場合によって変わりますが、この場合は終了コードになります。ヘッダファイルはこれまた環境依存の process.h です。というわけで、あまり詳しい解説はしたくないです。とりあえず、上のようにすればプログラムが実行できて、終了コードを受け取れることが保証されているということだけ言っておきます。

 それを踏まえて上のプログラムを見てみると、_spawnl の戻り値で分岐していますね。_spawnl の戻り値は終了コードになっているので、kuku.exe が正しく終了したかどうかで分岐していることになります。

 もう1つ、Linux のように BASH で分岐する例も示しておきます。BASH の if 文は

if <条件文>; then <条件文の戻り値が0のとき>; else <0でないとき>; fi

なので、

シェルプログラム
#!/bin/bash -f

if ./kuku; then
    echo "Ok."
else
    echo "Error."
fi

のようにして分岐できます。一連の作業をシェルに書いておいて、プログラムでエラーが発生すると作業を途中で終了するようにできるわけですね。


 環境依存の話が多かったのでなんともいいがたい回になりました。環境無依存な所をまとめると、


となります。

 余談ですが、void main を使っているかいないかでその参考書が良し悪しが大体判断できるらしいです。あんまりこういうことをいうと営業妨害になるかもしれませんが、参考書を選ぶ際に心にとめておくといいかもしれませんね。


第40章 二世帯住宅 | 第42章 私はαでありωである2

Last update was done on 2001.4.20

この講座の著作権はロベールが保有しています