メモリ管理の問題
概要
メモ
このトピックの例はインターリーブされた複素数 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 とデストラクターの両方で同じメモリを解放することを意味し、メモリを破損する可能性があります。