1. メモリ領域はどれくらいまで確保できるか 以下では非常に大きな配列を動的に作成して、new演算子が例外を発生する実験を行 います。 実験はWindows 95 パソコンで、Borland C++ Builder 3 のコマンドライン・コンパ イラ bcc32.exe を使います。普通、動的メモリ領域は実メモリ(RAM)に作成され ますが、要求された領域が足りないとき Windows 95 ではハードディスクのスワップ・ ファイルを仮想メモリとして利用します。つまり可能なメモリ領域の大きさは、実メモ リとハードディスクの空き領域の大きさで決まります。 以下の例では、約500Mバイトまでの配列領域を作成することができます。このと きハードディスクの空き領域はスワップファイルで埋め尽くされます。そしてハードデ ィスク内にそれ以上の大きさの配列領域を作ろうとしたときに、new演算子は失敗して 例外を発生します。 [注意]この様な実験はシステムに及ぼす危険性が無いとはいえません。またプロ グラムの終了後、ハードディスクの中のスワップ・ファイル(win386.swp)が 大きいまま残っていて、ディスクの空きがほとんど無い状態になる場合がありま す(再起動すると通常のスワップ・ファイルのサイズに戻ります)。 そのようなときは、Windowsを終了させMS-DOSモードで再起動して、Windows ディレクトリにあるスワップ・ファイルWIN386.SWPをMS-DOSコマンドDELで削除 します。スワップ・ファイルはWindowsのMS-DOSプロンプトからは消すことはで きません。スワップ・ファイルは通常のときでもある程度のサイズを持っていま す。削除するのはそれが異常に大きくなった場合です。削除する前にはサイズを 確認してから行います。 次のプログラムは配列の大きさを入力して、整数型の配列を作成します。成功すると 作成された領域のバイト数を表示して、領域を解放し終了します。また失敗するとメッ セージを表示してプログラムを異常終了(アボート)します。
// except.cpp // new演算子例外の実験プログラム(1) #include <iostream.h> int main() { int *ptr, n; try{ cout << "割り当てるメモリサイズを入力してください:" << endl; cin >> n; ptr = new int[n]; // 例外が発生すると、この下のコードは実行されない cout << "メモリサイズは: " << n * sizeof(int) << " バイトです。" << endl; } catch(bad_alloc){ // 例外 bad_alloc をここで受け取る cerr << "bad_alloc 例外を受け取りました。" << endl; abort(); } catch(...){ // それ以外の例外はここで受け取る cerr << "その他の例外を受け取りました。" << endl; abort(); } delete [] ptr; cout << "メモリを解放しました。" << endl; return 0; } |
C:\Source>except 割り当てる配列のサイズを入力してください: 123456789 メモリサイズは: 493827156 バイトです。 メモリを解放しました。 C:\Source>except 割り当てる配列のサイズを入力してください: 1234567890 bad_alloc 例外を受け取りました。 Abnormal program termination |
2. new演算子が失敗したときにヌルポインタを返すようにする new 演算子がメモリ割り当てに失敗したときの動作をプログラムでカスタマイズする ことができます。 関数 set_new_handler()で、例外の型(ハンドラ)を変更したり、関数を呼び出すよ うにできます。例えば、set_new_handler() の引数にユーザ定義の関数名を指定してお くと、newが失敗するとその関数が呼び出されます(後述)。 C++ Builder のコマンドコンパイラでは、引数に0(ゼロ)を指定すると、例外を送 出しない従来バージョンの new演算子として動作します。つまり、newが失敗するとヌ ルポインタを返します。それを示したのが次のプログラムです。
// except1.cpp // new演算子例外の実験プログラム(2) // newが失敗すると例外でなくヌルポインタを返す #include <iostream.h> int main() { set_new_handler(0); // newが失敗するとヌルポインタを返す int *ptr, n; cout << "割り当てるメモリサイズを入力してください:" << endl; cin >> n; ptr = new int[n]; if(ptr == 0){ cerr << "メモリ割り当てに失敗しました。" << endl; abort(); } cout << "メモリサイズは: " << n * sizeof(int) << " バイトです。" << endl; delete [] ptr; cout << "メモリを解放しました。" << endl; return 0; } |
C:\Source>except1 割り当てる配列のサイズを入力してください: 1234567890 メモリ割り当てに失敗しました。 Abnormal program termination |
// except2.cpp // new演算子例外の実験プログラム(3) // アサーション #include <iostream.h> #include <assert.h> int main() { set_new_handler(0); // newが失敗するとヌルポインタを返す int *ptr, n; cout << "割り当てるメモリサイズを入力してください:" << endl; cin >> n; ptr = new int[n]; assert(ptr != 0); // アサーション cout << "メモリサイズは: " << n * sizeof(int) << " バイトです。" << endl; delete [] ptr; cout << "メモリを解放しました。" << endl; return 0; } |
C:\Source>except2 割り当てる配列のサイズを入力してください: 1234567890 Assertion failed: ptr != 0, file except2.cpp, line 15 Abnormal program termination |
3. newの例外処理をどのように行うべきか ここで示したサンプルは、大変簡単な例外処理を行っています。つまりメモリ領域の 確保に失敗すると直ちにプログラムを終了させます。配列を作ることができなければ、 それ以上実行するわけにはゆきません。あまり上品なやり方ではありませんが、妥当な 方法と言えます。 これは例外処理を何もしないときの、処理系が用意したデフォルトの動作と同じです。 catchブロックを用意していない場合は、new演算子が失敗すると例外は捕捉されませ ん。その場合は関数 terminate() が呼び出され、そしてこの関数はデフォルトでは、 関数 abort() を呼び出します。 つまりnewが例外を送出するコンパイラを使っている場合、何もしなくても最低限の エラー処理(プログラムの終了)は行ってくれます。この場合エラーメッセージを表示 できないだけです。 この意味では、次のようなコードを書くことも悪くはないでしょう。 int *ptr = new int[n]; assert(ptr != 0); 2行目のコードは、newが例外を送出するコンパイラを使っている場合は、実行される (アサーションが偽になる)ことはありません。しかし、ヌルポインタを返すコンパイ ラを使う場合の用心のために不要なコードとはいえません。例えば、Visual C++ のコ マンドライン・コンパイラはヌルポインタを返すようになっています。ヌルポインタを 返す場合はエラー処理を明示的に行ってプログラムを終了させる必要があります。 では、2次元配列の場合はどうでしょうか。「2.8 動的配列」で扱った最後のサンプ ルプログラム matrix07.cpp をもう一度考えましょう。 行列を構成するために(行数+1)個の動的配列を作成します。このときnew演算子 が失敗すると、既に領域を確保した配列があります(最初に失敗しなければ)。このよ うなときは例外処理でアボートする前に、既存の領域を解放しておくべきでしょう。 次のプログラムは、領域確保に失敗した配列を識別するために変数countを用意して、 これを使って、それまでに作成した領域を解放します。
// matrix08.cpp //行列の例外処理 //例外が発生した場合、既に作成した領域を解放する #include <iostream.h> int main() { int row, col; // 行と列の大きさ cout << "行の大きさを入力してください: "; cin >> row; cout << "列の大きさを入力してください: "; cin >> col; int i,j; // 領域の動的確保 int **mat; // ポインタを指すポインタ int count = -1; // 例外が発生した場所を識別する try{ mat = new int*[row]; // 行を作る for(i = 0; i < row; i++){ // 列を作る count = i; mat[i] = new int[col]; } } catch (bad_alloc){ // ここにエラー処理コードを書く cout << "メモリ割り当てに失敗しました。\n"; if(count >= 0){ // 既に作成した領域を解放する cout << count + 1 << " 行ベクトル\n"; while(--count >= 0) delete [] mat[count]; delete [] mat; } else cout << "行のポインタ配列\n"; abort(); // 異常終了させる } cout << row << " 行 " << col << " 列の行列を作成しました。"; //領域の解放 for(i = 0; i < row; i++) delete [] mat[i]; // 列を解放 delete [] mat; // 行を解放 return 0; } |
C:\Source>matrix08 行の大きさを入力してください: 123 列の大きさを入力してください: 1234567 101 行ベクトルでメモリ割り当てに失敗しました。 Abnormal program termination |
4. 例外処理で関数を呼び出す方法 例外処理は、3つのキーワードtry、throw、catchを使います。throwは送出する 例外を決めるキーワードです。次の例は、例外が発生したときにデフォルトの動作では なく、ユーザ定義の関数 out_of_memory() を呼び出します。そしてこの関数で例外の 型を throw(投げる)ようにします。
// except3.cpp // new演算子が失敗したときの動作を定義する #include <iostream.h> void out_of_memory() { cerr << "メモリ割り当てに失敗しました。" << endl; throw bad_alloc(); } int main() { set_new_handler(out_of_memory); int *ptr, n; cout << "割り当てるメモリサイズを入力してください:" << endl; cin >> n; ptr = new int[n]; cout << "メモリサイズは: " << n * sizeof(int) << " バイトです。" << endl; delete [] ptr; cout << "メモリを解放しました。" << endl; return 0; } |
C:\Source>except3 割り当てる配列サイズを入力してください: 1234567890 メモリ割り当てに失敗しました。 Abnormal program termination |
Copyright(c) 1999 Yamada,K