メモリ管理の問題
概要
メモ
このトピックの例はインターリーブされた複素数 API で関数を使用します。これらの関数でアプリケーションをビルドするには、リリース固有のオプション -R2018a で mex を呼び出します。
MEX 関数から MATLAB® に制御が戻るとき、左辺の引数 plhs[] に含まれる出力引数 mxArray に計算の結果が返されます。これらの配列には一時的なスコープがなければならないので、関数 mexMakeArrayPersistent で作成された配列を plhs には渡さないでください。MATLAB は、plhs 内にない MEX 関数によって作成された mxArray があれば、それを破棄します。MATLAB は、関数 mxCalloc、関数 mxMalloc または関数 mxRealloc を使用して MEX 関数に割り当てられたメモリもすべて解放します。
一般に、MathWorks® では、MEX 関数が独自の一時的な配列を破棄し、動的に割り当てられた独自のメモリを解放することを推奨します。自動メカニズムに依存するより、ソース MEX ファイルでこのクリーンアップを実行する方がより効率的です。この方法は、他の MATLAB API アプリケーション (自動クリーンアップ メカニズムのない MAT ファイル アプリケーション、エンジン アプリケーション、MATLAB Compiler™ の生成するアプリケーション) と整合性があります。
ただし、ソース MEX ファイルの mxArray が以下の場合は、これを破棄しないでください。
右辺のリスト
prhs[]で MEX ファイルに渡される場合左辺の引数リスト
plhs[]に返される場合関数
mexGetVariablePtrによって返される場合構造体の作成に使用される場合
この節では、メモリ管理固有の状況について説明します。ソース MEX ファイルのコードを確認し、次のような状況下ではこれらの関数を使用しないことをお勧めします。詳細については、MEX ファイルでの一時配列の自動クリーンアップと永続的な mxArrayを参照してください。メモリ問題の詳細は、メモリを効率的に使用するための対策を参照してください。
メモリ管理の問題となり得るものには以下があります。
mxArray の不適切な破棄
mxArray の破棄に関数 mxFree を使用しないでください。
例
次の例では、関数 mxFree は配列オブジェクトを破棄しません。この操作によって、配列に関連付けられている構造体ヘッダーは解放されますが、MATLAB は配列オブジェクトの破棄が必要であるかのように動作し続けます。したがって、MATLAB は、配列オブジェクトの破棄を試み、その過程で構造体ヘッダーを再度解放しようとします。
mxArray *temp = mxCreateDoubleMatrix(1,1,mxREAL);
...
mxFree(temp); /* INCORRECT */解決法
代わりに関数 mxDestroyArray を呼び出します。
mxDestroyArray(temp); /* CORRECT */
セルまたは構造体 mxArray の不適切な作成
prhs[] をメンバーの配列として、関数 mxSetCell または関数 mxSetField のバリアントを呼び出さないでください。
例
次の例では、MEX ファイルが戻ると、MATLAB は cell 配列全体を破棄します。これには cell のメンバーが含まれるので、MEX ファイルの入力引数は暗黙的に破棄されます。これは、いくつかの奇妙な結果の原因となる可能性があり、使用される右辺の引数が一時的な配列 (リテラルや式の結果など) である場合は、一般に呼び出し側ワークスペースの破損に関係します。
myfunction('hello')
/* myfunction is the name of your MEX file and your code
/* contains the following: */
mxArray *temp = mxCreateCellMatrix(1,1);
...
mxSetCell(temp, 0, prhs[0]); /* INCORRECT */解決法
関数 mxDuplicateArray を使用して右辺の引数のコピーを作成し、そのコピーを関数 mxSetCell (または関数 mxSetField のバリアント) への引数として使用します。以下に例を示します。
mxSetCell(temp, 0, mxDuplicateArray(prhs[0])); /* CORRECT */
不適切なデータによる一時的な mxArray の作成
データが API ルーチンによって割り当てられていない mxArray で関数 mxDestroyArray を呼び出さないでください。
例
mxSetDoubles、mxSetComplexDoubles、または mxCalloc、mxMalloc、mxRealloc によって割り当てられていないメモリを対象のデータ ブロックとして指定して (2 番目の引数) いずれかの型付きデータ アクセス関数を呼び出すと、MEX ファイルが戻ったときに、MATLAB は実数データおよび (ある場合は) 虚数データを指すポインターを解放しようと試みます。したがって、この例では、MATLAB はプログラム スタックからメモリを解放しようとします。
mxArray *temp = mxCreateDoubleMatrix(0,0,mxREAL);
double data[5] = {1,2,3,4,5};
...
mxSetM(temp,1); mxSetN(temp,5); mxSetDoubles(temp, data);
/* INCORRECT */解決法
関数 mxSetDoubles を使用してデータ ポインターを設定するのではなく、正しいサイズで mxArray を作成し、memcpy を使用して、関数 mxGetDoubles によって返されるバッファーにスタック データをコピーします。
mxArray *temp = mxCreateDoubleMatrix(1,5,mxREAL);
double data[5] = {1,2,3,4,5};
...
memcpy(mxGetDoubles(temp), data, 5*sizeof(double)); /* CORRECT */潜在的なメモリ リークの作成
Version 5.2 より前は、いずれかの API 作成ルーチンを使用して mxArray を作成してから、関数 mxSetDoubles を使用してデータへのポインターを上書きしても、MATLAB によって元のメモリが解放されていました。現在は、MATLAB ではメモリは解放されなくなっています。
以下に例を示します。
pr = mxCalloc(5*5, sizeof(double)); ... <load data into pr> plhs[0] = mxCreateDoubleMatrix(5,5,mxREAL); mxSetDoubles(plhs[0], pr); /* INCORRECT */
この例では、5*5*8 バイトのメモリがリークします。ここで、8 バイトは double のサイズです。
このコードを次のように変更することでメモリ リークを回避できます。
plhs[0] = mxCreateDoubleMatrix(5,5,mxREAL); pr = mxGetDoubles(plhs[0]); ... <load data into pr>
または
pr = mxCalloc(5*5, sizeof(double)); ... <load data into pr> plhs[0] = mxCreateDoubleMatrix(5,5,mxREAL); mxFree(mxGetDoubles(plhs[0])); mxSetDoubles(plhs[0], pr);
最初の解決法の方が効率的です。
同様のメモリ リークは、mxSetDoubles、mxSetComplexDoubles、mxSetIr、mxSetJc またはいずれかの数値の型付きデータ アクセス関数を使用する場合にも発生する可能性があります。この節で説明したようにコードを変更することでメモリ リークを回避できます。
構造体の不適切な破棄
構造体では、mxDestroyArray をフィールド データ配列に対してではなく、構造体に対してのみ呼び出さなければなりません。構造体内のフィールドは、関数 mxSetField または関数 mxSetFieldByNumber によって使用される配列内のデータを指します。関数 mxDestroyArray は構造体を破棄するとき、その構造体の全体を調べ、データ配列内のメモリなどその他のデータをすべて解放しようとします。データ配列ごとに mxDestroyArray を呼び出すと、同じメモリが 2 回解放され、これによってメモリが破損することがあります。
例
次の例では 3 つの配列を作成します。1 つの構造体配列 aStruct と、2 つのデータ配列 myDataOne および myDataTwo を作成します。フィールド名 one には myDataOne 内のデータへのポインターが、フィールド名 two には myDataTwo 内のデータへのポインターが格納されています。
mxArray *myDataOne;
mxArray *myDataTwo;
mxArray *aStruct;
const char *fields[] = { "one", "two" };
myDataOne = mxCreateDoubleScalar(1.0);
myDataTwo = mxCreateDoubleScalar(2.0);
aStruct = mxCreateStructMatrix(1,1,2,fields);
mxSetField( aStruct, 0, "one", myDataOne );
mxSetField( aStruct, 1, "two", myDataTwo );
mxDestroyArray(myDataOne);
mxDestroyArray(myDataTwo);
mxDestroyArray(aStruct); /* tries to free myDataOne and myDataTwo */
解決法
コマンド mxDestroyArray(aStruct) で、3 つすべての配列内のデータを破棄します。
... aStruct = mxCreateStructMatrix(1,1,2,fields); mxSetField( aStruct, 0, "one", myDataOne ); mxSetField( aStruct, 1, "two", myDataTwo ); mxDestroyArray(aStruct);
C++ クラス デストラクター内のメモリの破棄
MEX 関数で使用されるクラスの C++ デストラクターで関数 mxFree または関数 mxDestroyArray を使用しないでください。MEX 関数がエラーをスローすると、MEX ファイルでの一時配列の自動クリーンアップで説明されているように、MATLAB は MEX ファイル変数をクリーンアップします。
オブジェクトがスコープ外になるエラーが発生すると、MATLAB により C++ デストラクターが呼び出されます。デストラクターでメモリを直接解放することは、MATLAB とデストラクターの両方で同じメモリを解放することを意味し、メモリを破損する可能性があります。