第二回-05 : オブジェクトの代入

本ページではオブジェクトの代入について解説する。


stack クラスのオブジェクトの代入

まず、stack クラスのオブジェクトの代入について解説しよう。
今、二つのスタック s1 、s2 があるとする。



図にあるように、代入「s2=s1;」において s1 のメンバ変数が全て s2 に代入される。
一般に、オブジェクトの代入を行うと上の例のようにクラスのメンバ変数が全てコピーされる。

しかし、メンバ変数にポインタが存在する場合、単にメンバ変数をコピーするだけでは 問題が起こる場合がある。
それが以下に示す strtype における例である。

strtype クラスのオブジェクトの代入

strtype クラスのオブジェクトの代入について解説する。
ソースコードは、第二回-04のソースコードの内、「以下の三行は次のページ (第二回-05) で有効にすること」の三行を有効にすることで得られる。

命令「s2=s1;」の前後で、オブジェクト s1, s2 の状態は次の図のように変化する。



上でみたように、「s2=s1;」によってクラスのメンバ変数の値が全て代入される。
strtype クラスはメンバ変数にポインタを含むが、このポインタの代入 (模式的に書けば s2.p = s1.p) によって s2 のポインタの参照先が変化する。

p が参照しているメモリの内容が代入されるわけではないことに注意しよう。
(メンバ関数が配列である場合とポインタである場合の違い)

なお、この時点ではアプリケーションにエラーは起きない。(一つのメモリ領域を 2 つのポインタが指していても問題はない)

エラーが起きるのはプログラムが終了する直前、s1 と s2 が解放されるときである。 プログラムが終了する際、s2 および s1 のデストラクタが呼ばれることは既に学んだ。
いま、「s2 のデストラクタ→ s1 のデストラクタ」の順に呼び出されるとしよう。



s2 のデストラクタが呼ばれると、上図左のようにオブジェクト s2 および s2 の p が指していた「This is a test.」という文字列を格納したメモリ領域が解放される。
次に s1 のデストラクタが呼ばれるが、このとき、s1 の p が指していたメモリ領域は すでに解放されている。
そのため、s1 のデストラクタは「既に解放されているメモリ領域をさらに解放」しようとする。これはメモリの二重解放となり、エラーになる。

このエラーに対してどのような現象が起こるかは環境依存 (主にに OS 依存) となる。
アプリケーションが警告を発して異常終了する環境もあれば、何も警告を出さずエラーに気づかない環境もあるだろう。
しかし、上の図と解説を見るとこのプログラムの振舞いに問題があることは明らかであろう。
なお、2002 年の 3 月に、linux などで用いられる圧縮ライブラリ zlib の脆弱性が発見されたが、
これは上記の「メモリの二重開放」に関係している。

参考:zlib 圧縮ライブラリに脆弱性。Linux ほか多数のプログラムに影響 (ITmedia)
参考:オープンソースソフトの脆弱性が Microsoft 製品にも影響 (ITmedia)
(残念ながらこれらは既にリンク切れである)

まとめると、このプログラムの問題点は以下のようになる このように、メンバ変数にポインタがある場合のオブジェクトの代入には注意しなければならない。

本来であれば、「s2=s1;」なる命令によって実現して欲しいのは下図のような振舞であろう。



このような振舞いをさせるためには「代入演算子の多重定義 (オーバーロード)」の知識が必要であり、今後学ぶことになるだろう。

←第二回-04 : メモリとポインタ第二回課題→

第二回演習用 Web ページへ

クラスから入る C++ へ戻る