なぜ今日書く気になってみた、newとdeleteにまつわるお話。
DLLでのnewとdelete
DLLを作っているときにちょっと注意が必要なのが、
DLL内部のコードでnewを行ったオブジェクトとは必ずDLL内部のdeleteを使って解放すること
です。これはある程度有名な話ですね。
どういうことか、というと、new演算子は対象のオブジェクトに対して以下の動作を行います。
- オブジェクトを作成するのに必要なメモリを確保する(オブジェクトのoperator newを呼び出す)
- オブジェクトのコンストラクタを呼び出してオブジェクトを初期化された状態にする
delete演算子はその反対で、
- オブジェクトのデストラクタを呼び出してオブジェクトが所有しているデータを解放する
- オブジェクトが使用していたメモリを解放する(オブジェクトのoperator deleteを呼び出す)
という動作を行います。問題なのはそれぞれのoperatorの動作です。
operatorが記述されていないときは、このメモリ確保・解放動作は連結されたランタイム上で行われます。
問題は、DLLを連結しているプログラムとDLLとで同じランタイムを使用していないとき、確保、解放の動作が異なってしまう可能性がある、という点です。
これらのかみ合わせに失敗すると、メモリリークを起こしたり、領域を解放しすぎたりしてすごい状態を引き起こすことになります。注意しましょう。
この状態はCOMのインターフェイスであるIUnknownでは発生しないようになっています。(メモリ解放処理であるReleaseがDLL側で記述できるため)
配置newと配置delete
静的なメモリや、メモリプールの目的などで大量のメモリがある場所からメモリを切り出してオブジェクトを初期化するときに使用するのが配置newです。
配置newはオブジェクトにoperator newが記述されていないとき、指定されたメモリアドレスをオブジェクトの先頭アドレスとして初期化する、という処理を行います。
これは、コンストラクタがプログラマが直接呼び出せない、newがコンストラクタを呼び出して初期化するという仕様をうまく使った処理です。
もちろん、operator newがオブジェクトで定義されているなら「指定されたメモリアドレスをオブジェクトの先頭アドレスとして初期化する」はこの限りではありません。
配置deleteは配置newがオブジェクトで定義されているとき、これと対になるために存在しています。
デストラクタに関しては、実はプログラマが直接呼び出すことが許されています。そのため、配置new時にメモリが改めて確保されないことがわかっているなら
デストラクタのみを呼び出せばよくて、配置deleteを呼び出す必要はありません。対象のオブジェクトの処理がわからないなら安全のために記述しておくとよいと思います。
それぞれこんなコードですね。
#include <new> class CItem{ public: ・・・ void *operator new (size_t size); //通常のoperator new void *operator new (size_t size,void *buf); //配置new用のoperator void operator delete (void *ptr); //通常のoperator delete void operator delete (void *ptr,void *buf); //配置delete用のoperator ・・・ }; //静的なオブジェクトのメモリ static unsigned char itemmem[sizeof(CItem)]; //メモリを指定して配置newを使いオブジェクトを確保 CItem *item = new(&itemmem[0]) CItem; //デストラクタを強制呼び出し item->~CItem(); //配置deleteでオブジェクトを解放 //delete(&itemmem[0]) item;
配置newや配置deleteを使うときはnew(new.h)のインクルードが必要になりますので注意を。
配置newは前述したとおり、静的なメモリや、メモリプールからのオブジェクト確保で使用します。
特に、静的なメモリ上のオブジェクトは初期化順序や解体順序が不定となってしまうため、必要に応じて配置newを使うことで、
静的なメモリを使用してかつオブジェクトの初期化順番をうまく制御することが出来ます。解体を忘れないように。