cv::flann::Index クラスを使うとヒープが壊れる
#include "cv.h"
/*
【概要】
OpenCV の opencv2/flann/miniflann.hpp で定義されている
cv::flann::Index クラスのインスタンスをヒープ上に作成すると、
delete 時にメモリ関連の例外が投げられ、プログラムが異常終了します。
【環境】
・OpenCV 2.4.9, 64-bit, DLL 版
・Visual C++ 2010 + SP1
・Windows 7 Professional 64-bit
【現象】
Visual C++ 2010 で次のコードを実行すると、
delete 時に「ヒープが壊れている」という例外が発生します。
cv::flann::Index* index = new cv::flann::Index();
delete index;
デフォルト コンストラクタ以外のコンストラクタを使っても
同じ現象が発生します。
おそらくデバッグ時のメモリ チェック機能なので、
Debug モードのみで発生するものと思われますが、
Release モードで例外が出ないからと言って、
正常動作しているかどうかは分かりません。
【原因】
これはおそらく、Visual C++ 2010 のバグです。
OpenCV のソースコードを解析し、ステップ実行による調査を行いましたが、
ソースコードには問題は見られませんでした。
【解決方法】
試行錯誤の結果、cv::flann::Index クラスの virtual デストラクタを、
非 virtual デストラクタにすれば正常動作するようです。
miniflann.hpp のクラス宣言のデストラクタにおいて
virtual を削除すると正常動作します。
ただし、この方法では、次の問題があります。
(1) その OpenCV を使用している他のコードが virtual 関数を呼び出していると、
仮想関数テーブルのエントリがずれてしまうため、不具合が発生します。
(2) また、他のコードが Index クラスの派生クラスを実装していると、
仮想デストラクタが呼ばれずに、メモリリークなどの不具合が発生します。
問題 (1) は OpenCV と関連プログラムをすべて再ビルドすれば解決しますが、
問題 (2) は再ビルドしても解決しません。
そこで、Index クラスを修正せずに、
以下のような派生クラス MyIndex を定義して使えば、不具合は発生しません。
MyIndex クラスは継承しないことを前提としているため、
デストラクタは virtual にしません。
*/
/**
* OpenCV 2.4.9 + Visual C++ 2010 の不具合を回避するための代替クラスです。
* cv::flann::Index クラスの代わりにこのクラスを使えば、不具合は発生しません。
*/
class MyIndex : public cv::flann::Index
{
private:
// コピー コンストラクタはとりあえず封印します。
// 必要なら慎重に実装してください。
MyIndex(const MyIndex&);
MyIndex& operator=(const MyIndex&);
public:
MyIndex() : Index() {}
MyIndex(cv::InputArray features,
const cv::flann::IndexParams& params,
cvflann::flann_distance_t distType=cvflann::FLANN_DIST_L2)
: Index(features, params, distType)
{
}
// 非 virtual にします。
~MyIndex()
{
}
};
int main( int argc, char** argv )
{
// 例外が発生する例。
if (false)
{
cv::Mat mat = cv::Mat(100, 100, CV_32F);
cv::flann::IndexParams& params = cv::flann::AutotunedIndexParams(0.9F, 0.01F, 0, 0.1F);
cv::flann::Index* f1 = new cv::flann::Index();
delete f1;
}
// スタック上に作成する場合は、正常動作します。
{
cv::Mat mat = cv::Mat(100, 100, CV_32F);
cv::flann::IndexParams& params = cv::flann::AutotunedIndexParams(0.9F, 0.01F, 0, 0.1F);
cv::flann::Index f2;
}
// MyIndex を使うと、例外が発生しなくなります。
{
cv::Mat mat = cv::Mat(100, 100, CV_32F);
cv::flann::IndexParams& params = cv::flann::AutotunedIndexParams(0.9F, 0.01F, 0, 0.1F);
MyIndex* f3 = new MyIndex(mat, params);
delete f3;
}
return 0;
}