l VisualC メモリの確保と例外処理 '97.00.00
l.お知らせ 内容 LINK FILE   HTML Win PC Unix MS-DOS C C++ Mfc Java 
目次.C++言語_ 1 クラス 2 派生クラス 5 演算子のオーバーロード クラス(1)
 準備 3 オブジェクト 6 テンプレート 8 etc クラス(2)
 CC++ 4 フレンド・多重継承 7 メモリ確保・例外処理 未使用
メモリの確保と解放   new と delete のオーバーロード   コンストラクタでメモリを確保   例外処理   new の例外処理 

T メモリの確保と解放

new と delete 演算子を使って、次のようにメモリの確保と解放ができます。
C1 *p;
p=new C1;
delete p;
// クラス用 C1 のポインタ変数を宣言
// クラス用 C1 のメモリを確保して、そのアドレスを p に代入
// 使い終わったらメモリを解放します

new と delete に使われている関数
void *malloc(size_t n)
void free ( void *p )
// n バイトを確保します。
// p のメモリを解放します。
malloc の返り値は、確保したメモリの先頭アドレス。確保できなかったときは、0 を返します。
void * : データの型を指定しない、メモリのアドレスそのもの。

free()でメモリを解放すると、その位置のメモリは別な用途に使用されます。
従って再び解放すると、別なデータなどを消してしまうことになります。
0 を指定すると delete は何もしないことになっているので、 free()でメモリを解放したらポインタ p を p=0 にしておけば安全です。

メモリを解放する前に同じポインタを使ってメモリを確保すると、 前に確保したメモリを解放する方法がなくなってしまうので、 別なメモリを確保するときは別なポインタ変数を使います。

malloc と free の用例
#include <iostream.h>:     /*  malloc と free  */
#include <malloc.h>:    // malloc と free を使うために必要。 stdlib.h でも可。  

void main( )
{ int *p;                 // int 型のポインタ変数 p を宣言
  p=(int *)malloc(4*3);   // int 型のデータ3個分のメモリを確保
//p=(int *)malloc(sizeof(int)*3);   // これでも可
  // malloc の返り値は void * 型なので、int * 型にキャストします。
  *p=5; p[1]=6; *(p+2)=7; // 確保したメモリに代入
  cout << *p << *(p+1) << *(p+2) << endl; // 確認 567
  free(p);                // メモリ解放
  p=0; // ← 再び free(p); としても誤動作しないため
int q; cin >> q;
}


T new と delete のオーバーロード
new と delete は演算子なので、別な機能をオーバーロードすることができます。

極端な例
次のように書式さえ合わせれば、メモリの確保とは無関係な機能にも設定できます。

#include <iostream.h> /*  new に全く違う機能を設定  */
#include <stdlib.h> // size_t を使うために必要。malloc でも可。
class C1
{ public:
  void *operator new( size_t n )
  { cout << "メモリは確保しません。\n"; return 0;}
};

void main( )
{ C1 *OB = new C1 ; } // C1 型のメモリを確保 しません。

オーバーロードの書式は
演算子のオーバーロードの場合と同じです。

new の返り値は void * 型とします。
new を呼び出すときには、返り値が自動的に必要な型にキャストされます。
new の仮引数は size_t 型にします。
( size_t は unsigned int の別名です。malloc.h と stdlib.h に宣言されています。)
new を呼び出すときには、指定した実引数が自動的に size_t 型にキャストされます。
delete の仮引数は new と同じ型とします。
#include <stdlib.h> // malloc( ) free( )を使うために必要
void *operator new (size_t n) { void *p=malloc(n); return p; }
void operator delete (void *p) { free(p); }

グローバルな new と delete のオーバーロード
デフォルトで設定されている new と delete を置き換えます。

次の例は、オーバーロードされていることが確認できるように、確保と解放の文字を表示しています。
iostream.h には、オーバーロードできない delete が入っているようなので、stdio.h を使っています。
オーバーロードには stdio.h は必要ありませんが、結果を表示するために使います。
#include <stdio.h>  // (printf, gets)    /* グローバルな new, delete */
#include <malloc.h> // (malloc, free)
void *operator new( size_t n )    // new 演算子のオーバーロード
{ printf("確保\n"); void *p = malloc( n ); return p; }
void operator delete( void *p )   // delete 演算子のオーバーロード
{ printf("解放\n"); free(p); p=0; }

void main( )
{ int *P;      // int 型のポインタ変数を宣言
  P=new int; // int 型データ1個分のメモリを確保して、P にアドレスを代入
  *P=5;       // 確保したメモリの位置に 5 を書込む(代入する)
  printf("%d\n",*P); // 確認 5
  delete P;   // メモリを解放します
  char q[10]; gets(q);
}

クラス用の new と delete のオーバーロード
クラスのメンバ関数として operator new と operator delete をオーバーロードすると、 そのクラス型のデータ(=オブジェクト)用のメモリを確保する場合には、 オーバーロードした new と delete が呼び出されます。
それ以外のデータ型のときには、グローバルな new と delete が呼び出されます。

次の例はメモリ上に(プログラム本体とは別に)オブジェクトを構築します。
new で構築したので、オブジェクトの名前がありません。ポインタ pOB を使って操作します。
#include <iostream.h> // (cout, cin)     /*  クラス用の new と delete  */
#include <stdlib.h>   // (malloc, free)
class C1
{ public: int a; char s[10]; int *P;
  void *operator new( size_t n )   // new 演算子のオーバーロード
  { cout << " 確保-"; void *p = malloc( n ); return p; }
  void operator delete( void *p )  // delete 演算子のオーバーロード
  { cout << "-解放 " << endl; free(p); p=0; }
};

void main( )
{ C1 *pOB ;       // C1 型のポインタ変数を宣言
  pOB = new C1;   // C1 型のメモリを確保。ポインタを pOB にアドレスを代入。
  pOB->a=5;       // (*pOB).a に 5 を代入
  pOB->s[0]='A';  // (*pOB).s[0] に 'A' を代入
  pOB->s[1]='B'; (*pOB).s[2]='C'; (*pOB).s[3]='\0';
  cout << pOB->a << (*pOB).s; // 確認  5ABC
  delete pOB;     // メモリを解放します。
  int X; cin >> X;
}

T コンストラクタでメモリを確保
コンストラクタで new を呼び出すようにすれば、メモリの確保とオブジェクトの初期化がいっしょにできます。
また、デストラクタで delete を呼び出すようにすれば、必要がなくなったときに自動的にメモリが解放されます。

次の例はコンストラクタで、必要なサイズだけのメモリを確保します。
new と delete はオーバーロードしていません。
プログラムの終了時に、自動的にデストラクタが呼び出されて、メモリを解放します。
#include <iostream.h> // (cout)        /* コンストラクタでメモリを確保 */
#include <string.h> // (stcpy, strlen)
class C1                // クラス C1 の宣言
{ public: char *t;      // メンバデータ (メモの確保用のポインタ変数 t )
   C1(char *s)          // コンストラクタ
   { unsigned int n=strlen(s); // n に入力文字長を代入。
     t = new char[n+1]; // メモリ確保。( +1 は \0 の分)
     strcpy(t,s);
	}
  ~C1( )                 // デストラクタ。メモリ開放。
  { delete [] t; cout << "デストラクタで解放\n"; }
};

void main( )
{ char S[4]="ABC";   // 文字配列 S を宣言/初期化
  C1 OB1(S);	     // クラスのオブジェクトを宣言(メンバデータはポインタ1個)
  // コンストラクタによって ABC が入る分だけメモリが確保され、格納されます。
  cout << S << endl; // 確認 ABC
int q; cin >> q;
} // main( ) の終了時にデストラクタが呼び出され、メモリが解放されます。

T 例外処理
コンピュータはメモリの内容を順番に読んで仕事をしています。
関数の呼び出しなどが行われるときは、読む位置が変りますが、それはメモリにそのように書かれているからです。
それとは別に、特別な処理を行わせるために、特別の記号と番号で、処理を指定できる機能があります。

それだけでは関数の呼び出しなどと同じですが、 コンピュータにはエラーが生じたときに特別の記号と番号を発生する機能があるので、 自動的にエラーを処理することができます。
このような例外的な処理は、例外処理と呼ばれます。
例外処理の原因(エラーなど)は、例外と呼ばれます。

例外(エラーなど)が発生すると、普通は OS(MS-DOS など)にある例外処理が呼び出されるようになっていて、 「エラーが発生しました」などのメッセージが表示されます。
エラーが発生したときに、自分で作った例外処理を呼び出すように、登録を変更することができます。

C++言語では、catch(...) で例外処理を登録します。
このとき、例外処理の対象範囲を try{ } で指定しておきます。

次の例は a/b の演算で0の除算をして、わざとエラーを発生させます。
b の値を0以外に変更すると例外は発生しません。
#include <iostream.h> /* 例外処理(エラーが発生したとき) */

void main( )
{ int a=5, b=0;
cout << "ここまではエラーが発生しても検出されません。\n";
try // エラー検出の、範囲の指定
{ cout << "ここからエラーの検出区間です。\n";
int x=a/b;
cout << "a/b=" << x << endl;
cout << "エラーの検出区間が終りました。\n";
}
catch( ... ) // 例外処理の内容
{ cout << "エラーが発生しました。\n"; }
cout << "ここからはエラーが発生しても検出されません。\n";
int q; cin >> q;
}

上記の例はすべてのエラーを例外として処理します。
どんなときに例外として扱うかは、自分で決めることができます。
この場合は、普通の条件判断と同じに 例外にしたい状態を判断し、throu で例外処理を呼び出します。
if( b==0 ){ throu b; }
普通の関数の呼び出しとは異なり、catch( ) を呼び出すのに throu と書きます。
またこの場合は catch( ) に引数が必要で、呼び出す throu は引数を ( )で囲みません。

次の例は、0での除算だけを例外処理します。
もしメモリが足りなくなったなどの他のエラーが発生したときは、OS の例外処理が呼び出されます。
#include <iostream.h> /* 例外処理( 0 による除算) */

void main( )
{ int a=5, b=0;
cout << "ここまではエラーが発生しても検出されません。\n";
try // エラー検出の、範囲の指定
{ cout << "ここからエラーの検出区間です。\n";
if( b==0 ) { throw b; } // b=0 なら例外処理を呼び出します。
int x=a/b;
cout << "a/b=" << x << endl;
cout << "エラーの検出区間が終りました。\n";
}
catch( int bb ) // 例外処理の内容
{ cout << "エラーの原因は b=" << bb << endl; }
cout << "ここからはエラーが発生しても検出されません。\n";
int q; cin >> q;
}
もしメモリが足りなくてプログラムを継続できないような場合には、 例外処理で exit( ) を呼び出し、終了させることもできます。

T new の例外処理
new でメモリが確保できなかった場合は NULL を返すので、上記のような方法で処理したり、例外処理を使用せずに処理することもできます。
しかし下記のように、new 専用の例外処理を設定することができます。

1.例外処理を関数として記述します。
 この関数は、返り値を int 型にし、引数は size_t 型にします。
 返り値の値は0にします。
 0以外にすると、再びメモリの確保を繰返します。
 引数は、必要がなくても書きます。
2.その関数を、_set_new_handler で 例外処理 として登録します。

_set_new_handler は、その前に登録されていた例外処理のポインタを返すので、必要なら保存して、後で元に戻します。
_set_new_handler が返すポインタは特殊な _PNH 型なので、代入用のポインタ変数の宣言には _PNH 型を使います。

次の例は、わざとメモリが不足するように、 new で約 1ギガバイトのメモリを確保しています。
メモリが足りない場合にはハードデスクを仮想メモリとして使うので、大容量のハードデスクを使っている場合には、もっと大きな値を指定する必要があります。
#include <iostream.h> /* new の例外処理 */
#include <new.h> // nwe の例外処理を行うために必要
int func(size_t n) // 例外処理用の関数を宣言
// 必要なら、引数名 (n) を書きます。引数は確保しようとしたバイト数が受け取れます。
{ cout << "メモリ確保不可。\n"; return 0; }
class C1 { public: char s[1000000000]; };

void main( )
{ _PNH ph = _set_new_handler(func); // 例外処理関数を登録
C1 *OB=new C1 ; // C1 型のメモリを確保します。
delete ( OB ) ; // メモリを解放します。
_set_new_handler( ph ); // 例外処理の指定を元に戻します。
int q; cin >> q;
}
確保できるかもしれないような半端なサイズを指定すると、 確保できるところまで確保してみようとするので、 テストするのに時間がかかります。

メモ
複数のメモリのブロックを確保する場合は、もし途中でメモリが足りなくなったら、プログラムを終了させる前にそれまで確保したメモリを全部解放する必要があります。
(例えばデストラクタを利用します。)


T

C++言語 6 テンプレート 7 メモリ確保・例外処理 8 etc
 
  mtoga@sannet.ne.jp   登録日 '96. 6.15
URL : http://www.page.sannet.ne.jp/mtoga/index.html