第43章 切り捨て御免

 今回はずいぶんと荒っぽいことをします。できれば使わないで済ませたいのですが、使わざるを得ない状況もあるかもしれません。

注)今回の話はC++というよりはC言語のお話です。この機能はC++ではなるべく例外処理で行うようにしてください。それではまずいというときだけ利用してください。


 というわけで、今回の要点はこんな感じです。


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


 先ず、どうしようもないエラーが発生したとします。そういう場合、即座にプログラムを終了しないことにはどうしようもなかったりします。つまりは、強制終了したくなるわけです。

 もちろん、C/C++言語には強制終了する関数が用意されています。それが今回お話しする exitabort です。

 では、両者にはどんな違いがあるのでしょうか? 先ず目に付く違いは exit は引数を持っていて、abort は引数を持っていないというところです。それ以外の違いは、実際に実行してみて確かめてみましょう。

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

void Func(bool b)
{
    if(b)
        abort();
    else
        exit(1);
}

int main()
{
    char letter;
    cin >> letter;
    Func(letter == 'y');
    cout << "dummy" << endl;
    return 0;
}
実行結果1
n
実行結果2
y

abnormal program termination
注)環境によって結果は異なります
VC++でも、場合によってはダイアログが表示されます

 両方とも dummy という文字を表示することなく終了しました。しかし、abort の方は「abnormal program termination(異常終了)」というなんだか物騒な表示が出ています。

 このように、同じ強制終了でも abort の方がきついということが分かります。

 では、この終了コードはどうなっているでしょうか? VC++では、デバッグ実行(F5)をしたときは「デバッグ」と書かれたウィンドウ内に終了コードが表示されます。これを見比べてみると、exit では 1 に、abort ではよく分からないけどいつも同じ値になっていると思います。

 では、exit の引数を変えて実行してみてください。今度はその値になっていると思います(環境によっては16進で上位数桁がちょん切られていることもあるかもしれません)。このように、exit の引数は終了コードに使われます。abort では終了コードは固定になります。


 と、ここまでは実に表面的な話です。exit と abort の重要な違いは「同じ強制終了でも abort の方がきつい」というところにあります。では、具体的にどう違うのでしょうか?

 それは、次のプログラムを実行すれば分かると思います。

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

void Func(bool b)
{
    if(b)
        abort();
    else
        exit(1);
}

int main()
{
    char letter;
    cin >> letter;
    cout << "表示される?"
    Func(letter == 'y');
    return 0;
}
実行結果1
n
表示される?
実行結果2
y

abnormal program termination

 第1部第2章で、endl や flush を書いていないと文字が出力されないことがあると言いました。これは、cout << を行うごとに出力していては時間がかかるので、一旦バッファに溜めて、バッファが一杯になるか endl か flush が呼ばれるかプログラムが普通に終了するかすると出力するようになっているからです。こういった処理をバッファリングと呼び、バッファから出力することをフラッシュと呼びます。ファイルを扱うときにもよく使うテクニックです。

 さて、上のプログラムを見てみましょう。cout << "表示される?"; と書いてあるように、フラッシュを行っていません。つまり、この段階ではまだ文字は表示されていないわけです。そして、それ以降にフラッシュする命令がないので、フラッシュされるタイミングはもうプログラムの終わりしかありません。

 結果を見てみると、exit では表示されていますが、abort では表示されていないことが分かります。このように、exit はいろいろな終了処理を行ってくれるわけです。強制的に終わらせるのは main 関数だ、と考えてもいいと思います。

 これに対して abort では表示されませんでした。つまり、abort は終了処理を行わずにいきなりプログラムを終わらせることが分かります。これが exit と abort の大きな違いです。


 また、終了処理はバッファのフラッシュの他にも自分で登録することが出来ます。そのための関数が atexit です。戻り値の型が void で引数無しの関数を作り、それを atexit 関数に渡せば登録完了です。あとは登録した順のに実行されます。これは後に作られたオブジェクトのデストラクタが先に呼ばれるのと同じ理由です。ある終了処理を行ったがために、後の方の終了処理内で使う変数の内容が破棄される可能性があると困るからです。

 では、実際にプログラムを見てみましょう。

プログラム3
// Exit3.cpp
#include <iostream.h>
#include <stdlib.h>

void AtExit1(){ cout << "at exit 1" << endl; }
void AtExit2(){ cout << "at exit 2" << endl; }
void AtExit3(){ cout << "at exit 3" << endl; }
void AtExit4(){ cout << "at exit 4" << endl; }

int main()
{
    atexit(AtExit1);
    atexit(AtExit2);
    atexit(AtExit3);
    atexit(AtExit4);

    char letter;
    cin >> letter;
    if(letter == 'y')
        exit(1);

    return 0;
}
実行結果1
y
at exit 4
at exit 3
at exit 2
at exit 1
実行結果2
n
at exit 4
at exit 3
at exit 2
at exit 1

 見れば分かるように、この処理は普通に終了したときにも実行されます。exit に限らず、atexit は終了処理一般の登録に使われるわけです。exit を使うにしても使わないにしても行って欲しい終了処理を登録するのに atexit を使うわけです。

 abort ではこの atexit で登録した関数も実行されません。完全に異常終了なわけです。


 あと、C++では少し補足が必要です。

 先ず、こういった処理は大抵は例外処理で事足りるということです。ただ、abort のように完全にうち切りたいときには abort を使うこともあるかもしれません。

 また、今までに作ったオブジェクトのデストラクタは呼ばれません。ただし、main の外にあるオブジェクト(外部変数)のデストラクタは呼ばれます。main を強制終了する関数と言ったのは、このあたりを意識していたわけです。普通のデストラクタを呼ばれたくはないけど終了処理はしたいというときには、exit を使うこともあるかもしれません。どういう場合なのかは分かりませんが。

 何にしろ、C++では例外処理があるのでどちらを使えばいいかを考えてから exit や abort を使って欲しいということです。使ってはいけないとは言いませんが、使う前にちょっと考えて欲しいわけです。


 では、今回の要点です。


 では、次回まで。


第42章 私はαでありωである2 | 第44章 デバッグ文

Last update was done on 2001.5.4

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