Version 11.13
new と delete とそのほか色々
「今回は、 new と delete の残りの部分について見ておきます」
『まず前回の続きからよね』
「そう、 new と delete について。まず、正確に言うと、 new と delete
にはメモリを確保する機能はありません」
『ええっ!? だって、確保してるじゃない』
「それは、他の関数を呼び出してるから。 new には」
・メモリを確保する関数を呼び出す。
・コンストラクタを呼び出す。
「っていうふたつの機能があるんです。同じくデストラクタには」
・デストラクタを呼び出す。
・メモリを解放する関数を呼び出す。
「っていう機能があります」
『コンストラクタとデストラクタで順序逆~』
「だって、解放したあとじゃ変数ないんだから、デストラクタは呼べないで
しょ?」
『あ……そっか、そういえば』
「こういうのも重要だから気をつけてね。で、この new が呼ぶ〈メモリを
確保する関数〉とか delete が呼ぶ〈メモリを解放する関数〉って、実は自
由に変えられるんです」
『好きなのでできるってこと? たとえば malloc() とか?』
「そういうこと。で、この関数は、 MFC のプログラムなら……あ、見た方
が早いかな」
void NewAndDelete()
{
// int を作成。
int *pi = new int;
// そして解放。
delete pi;
}
「で、 new してるところにブレークポイントを置いて」
『ビルドして実行、んでステップイン!』
void* AFX_CDECL operator new( /* 略 */ )
{
return ::operator new( /* 略 */ );
}
『って関数に入った。 operator って確か、クラスに演算子が使えるように
するのだよね』
「そうそう、 Version 5.22 ( No.087 ) や Version 7.09 ( No.129 ) で
やったね。つまり new は演算子のひとつで、こういうふうに書くことで
new した時にこの関数を呼ぶようにさせることができるってこと」
『あれ、でも new したのって int だったよ。クラスじゃないじゃん』
「そう、これはクラス以外のものに new をした場合のもの。こういう
operator のは、別にクラスだけに使えるわけじゃないから」
『へー』
「 operator については今度ちゃんと説明するとして、この中でもう一段階
new を呼んでるでしょ」
『んじゃそっちに入るね。ほい』
void* __cdecl operator new( /* 略 */ )
{
// 略
return ::operator new(nSize);
// 略
pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);
// 略
}
『む、 new と、あと malloc() もどきがある』
「リリースビルドの時は上の方が、デバッグビルドの時には下の方が呼ばれ
ます」
『リリースビルド……ってなんだっけ』
「 Version 3.24 ( No.049 ) と Version 6.18 ( No.118 ) を参照」
『あ、プログラム切り替えるやつね。デバッグするときはデバッグモードに
しないと ASSERT() が使えないとか』
「そうそう。で、リリースビルドの時には普通の new が呼ばれます。これ
はあとで。デバッグモードの時には _malloc_dbg() が呼ばれます。これは
malloc() のデバッグ版」
『デバッグ版?』
「本当は正しい表現じゃないんだけどね。 Version 11.09 ( No.209 ) でメ
モリリークって教えたでしょ」
『メモリを確保しておいて解放しないのだよね』
「そうそう。あのとき、アプリを終了したときにメモリリークがあったって
表示されてたけど、その機能があるのは _malloc_dbg() の方なんです」
『え? だって、あのときだって普通に malloc() 使ってたよ?』
「実は、 malloc() はデバッグモードだと _malloc_dbg() を呼び出すんで
す」
『あ、そういうこと』
「この _malloc_dbg() については次回に説明するから置いておいて、結局
は new も malloc() 使ってるってこと」
『でもリリースビルドの時は new を使ってるんでしょ?』
「そっちも見ておいた方がいいか。実は、この new の機能は MFC を使っ
てるから、素の new を見るには MFC なしじゃないといけないんです」
『でもここで使ってるんでしょ?』
「でもそれはリリースビルドの時専用だから」
『あ、デバッグモードじゃないからステップインとかできない……』
「そういうこと。そこで、 Version 8.01 ( No.143 ) の、 SDK だけのを
引っ張ってきて」
// 最初に呼ばれる関数です。
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// int を作成。
int *pi = new int;
// そして解放。
delete pi;
// 略
}
「っていうふうにして、 new してるところでブレークポイント置いて」
『なんかかなり回りくどいね……ほい、んでステップイン!』
void * operator new( unsigned int cb )
{
return _nh_malloc( cb, 1 );
}
『ってのに入ったよ。あ、また malloc() もどき』
「これは素の new なんだけど、この素の new も」
『 malloc() 系を使っている、と』
「そういうこと」
『あ、でもさ、 malloc() 系も API のを使ってるんでしょ? だったら』
「そう、最終的には、メモリ確保はすべて API で行われてるってこと」
『それって前にも何度かあったよね。ランタイムや MFC とか使ってても、
結局本当のところは API を呼び出してるんだって』
「そういうこと。それが重要だから」
『……ん? じゃあなんで new と delete なんだっけ』
「コンストラクタとデストラクタ」
『そうだった。 new はコンストラクタを呼んでくれて、 delete はデスト
ラクタを呼んでくれるんだよね』
「その機能があるから、 new と delete は必ず使う必要があるってこと」
『なるほど』
「ただ、 new と delete には、実は大きな問題があるんです」
『げ、なにそれ』
「今まで new で int とかしか作ってこなかったけど、配列の時にはちょっ
と特殊になるんです」
void CreateNewArray()
{
// このデータが入る領域を確保します。
const char *const DATA = "あいうえお";
// まず配列を確保。
char *pch = new char[strlen( DATA ) + 1];
// コピーしてから出力。
strcpy( pch, DATA );
TRACE( "%s\n", pch );
// 解放します。
delete[] pch;
}
『サイズは [] の中で指定するんだ。配列っぽいね』
「うん、キャストも必要ないし、 new の方はわかりやすいんだけどね」
『ってことは delete ……あ、 delete にも [] が付いてる』
「実は、 new[] で配列を確保したら、必ず delete[] で解放しなきゃいけ
ないんです」
『 int 一個とかならいいんだよね、今まで見てると』
「そう、配列の時だけ」
『間違えて [] 付けないで解放しちゃったらどうなるの?』
「そうすると、デストラクタが最初の要素だけしか呼ばれないんです。たと
えば」
void CreateNewCStringArrayBad()
{
// まず配列を確保。
CString *pcStr
= new CString[10];
// 誤った方法で解放します。
delete pcStr;
}
「ってすると、残りの9個の CString は」
『デストラクタが呼ばれない……うわ、実行したらエラーになった』
「だから必須ってこと」
『ってことはさ』
malloc() - free()
GlobalAlloc() - GlobalFree()
new - delete
new[] - delete[]
『って、4ペアになるんじゃない?』
「そういうことだね。だから、 new と delete を使う時にも必ずクラスの
中に入れて、コンストラクタとデストラクタで確保と解放がされるようにす
るのがいいかな」
『って、そのコンストラクタとデストラクタを呼べるのが new と delete
で、なんか鶏が先か玉子が先かっぽい……』
/*
Preview Next Story!
*/
『なんか、難しくないけどややこしいってゆーか』
「ま、パターンなんだけどね」
『ペアとか?』
「そうそう。いろいろあっても、基本は同じだったり」
『細かいところは違っても、だいたい似たり寄ったりだもんね』
「というわけで次回」
< Version 11.14 メモリ確保とデバッグモード >
『につづく!』
「ま、とりあえずそういうのに慣れるのがまずは重要かも」
『なんかヤダ』
「げ」