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;
}
  
[戻る]