none
vectorのデストラクタについて RRS feed

  • 質問

  • vector のメンバ変数を持つクラスを、new してインスタンス生成。
    その後、delete すると「ヒープが壊れていることが原因として考えられます。」という警告が表示されます。

    vector を含んだクラスはVS2002 C++でビルドしたDLLファイルです。
    アプリは、VS2012 C++です。

    アプリがVS2010 C++の場合、この問題は発生しません。

    ●知りたいこと
    ・VS2012 C++のアプリで、この警告が出ないようにする方法。
    ・この警告が表示される原因。特にVS2012から vectorに関するデストラクタで何か変更があったのでしょうか?

    2013年11月26日 0:48

回答

  • 佐祐理さんが既に指摘していますが、やや補足すると、
    new deleteのオペレータは、

    1.コンパイラのバージョンによって動作が異なる場合があります。
    2.MFCの使用の有無によってオーバーロードされます。バージョンにも依存するかもしれません。
    3.デバッグ、リリースの構成の違いによって動作が異なります。

    従って、

    A.DLLでnewしたものはDLL内でdeleteするのが望ましい。MFCの場合は必須。
    B.EXEとDLLの構成を一致させたほうが良い。
    C.できたらDLL側もVS2012でリビルドしたほうが良い。
     コンパイラが警告をはく可能性がある。
    D.DLL側のヘッダーにinline展開されそうなnew deleteが無い事を確認。
    E.その他、
     E.1 二重delete
     E.2 配列等への範囲外アクセス。

    などの点を確認してみてはどうでしょう。

    2013年11月26日 4:38
  • > A.DLLでnewしたものはDLL内でdeleteするのが望ましい。MFCの場合は必須。

    ご指摘いただいたように、DLL内でメモリを解放するために、MFCと静的にリンクしてDLLを作成するようにしました。
    これで、Cランタイムの変更に影響を受けないで済みそうです。

    ●新しいファイルの構成
    VS2012 C++ アプリ
    VS2002 C++ 拡張DLL
    VS2002 C++ MFC と静的にリンクしたDLL(メンバにvector)

    この構成にすると問題は、発生しなくなりました。
    改めてDLLの種類について確認することができ、勉強になりました。

    • 回答としてマーク Logimo 2013年11月28日 5:07
    2013年11月28日 5:06

すべての返信

  • これだけでは原因はわかりません。本当にVS 2002 C++でビルドしたDLLであれば、新たに警告が発生することはないと思いますが。

    実はコンストラクタ・デストラクタはDLL内ではなくヘッダファイル上でinline展開され、VS2012 C++アプリ側で実行されていたりしませんか?

    # VC++は年々強化されているので、今まで検出できなかった問題を新たに検出できるようになった可能性はあります。

    2013年11月26日 2:59
  • 佐祐理さんが既に指摘していますが、やや補足すると、
    new deleteのオペレータは、

    1.コンパイラのバージョンによって動作が異なる場合があります。
    2.MFCの使用の有無によってオーバーロードされます。バージョンにも依存するかもしれません。
    3.デバッグ、リリースの構成の違いによって動作が異なります。

    従って、

    A.DLLでnewしたものはDLL内でdeleteするのが望ましい。MFCの場合は必須。
    B.EXEとDLLの構成を一致させたほうが良い。
    C.できたらDLL側もVS2012でリビルドしたほうが良い。
     コンパイラが警告をはく可能性がある。
    D.DLL側のヘッダーにinline展開されそうなnew deleteが無い事を確認。
    E.その他、
     E.1 二重delete
     E.2 配列等への範囲外アクセス。

    などの点を確認してみてはどうでしょう。

    2013年11月26日 4:38
  • 情報が不十分でした。
    VS2002 C++でビルドしたDLLファイルは、MFC 共有 DLLです。
    そのため、VS2012 C++のアプリ側でメモリ管理が行われていました。

    vectorは、クラスのデストラクタが呼ばれたときに自動的に解放されるはずですが、
    VS2012 C++ (アプリ側)のランタイムを使用してメモリの解放が行われています。

    vectorが含まれているクラスだけ発生するのは、VC++ 2012からvectorのサイズが
    変更になっていて、それが原因で発生しているような気がします。

    問題に関しては、解決の目処が立ちました。
    助言いただき、有難う御座いました。

    2013年11月28日 4:52
  • > A.DLLでnewしたものはDLL内でdeleteするのが望ましい。MFCの場合は必須。

    ご指摘いただいたように、DLL内でメモリを解放するために、MFCと静的にリンクしてDLLを作成するようにしました。
    これで、Cランタイムの変更に影響を受けないで済みそうです。

    ●新しいファイルの構成
    VS2012 C++ アプリ
    VS2002 C++ 拡張DLL
    VS2002 C++ MFC と静的にリンクしたDLL(メンバにvector)

    この構成にすると問題は、発生しなくなりました。
    改めてDLLの種類について確認することができ、勉強になりました。

    • 回答としてマーク Logimo 2013年11月28日 5:07
    2013年11月28日 5:06
  • ところで警告された問題を修正するという方向に考えが向かないのでしょうか…?
    # 警告を非表示にすればすべての問題が解決するとか?
    2013年11月28日 5:14
  • vector に対して、自前のアロケータを作成するということを考えた程度でしょうか。
    原因に対して、簡単に解決できる方法があったので、そちらを選択しました。

    2013年11月28日 5:46
  • 解決したのは良いのですが、方法が消極的に感じられ、やや心配です。
    VS2002からVS2012のポーティングのコストは重いとは予測できますが、
    やはり、根本解決のためにはDLLのコードはポーティングした方が良いと考えられます。

    というのも、小刻みに開発プラットホームを上げていかなかったため、
    跳躍的ポーティングがやむをえなくなり、そのコストと困難性から、
    捨てらたり、放置されてしまうという例を、いくつか知っているからです。

    2013年11月28日 8:00
  • ライブラリをVS2012にアップグレードして、問題が発生しないことは確認しています。
    この方法は、他の方法では解決できない場合の最終手段として考えていました。

    その理由ですが、今回はライブラリまでがプロダクトの範囲であり、アプリはプロダクトの範囲外にあるためです。
    そのためアプリが、VS2012 C++で作成されるとは限りません。

    それともVSのバージョンに合わせて、ライブラリを提供していく方が良いのでしょうか。
    VSのバージョンに合わせて、ライブラリを提供していれば、今回のような問題は発生しなかった訳ですが。

    2013年11月29日 6:13
  • >それともVSのバージョンに合わせて、ライブラリを提供していく方が良いのでしょうか。

    判断できません。公開される製品の性質によると考えられるからです。

    個人的には、DLL「だけ」を外部に提供する場合、次のような方針でやってます。
    以下、全て「可能であれば」の条件付きですが、

    1.CRTとWin32SDKのみで構築する。
    2.公開するヘッダーにコードを含めない。
      開発用と公開用のヘッダーは別の場合もありえる。
    3.classをexportしない。
    4.場合によっては、C言語インターフェースのみ使用する。
    5.場合によっては、defによるEXPORTSを使用する。

    やむを得ずMFCのコードを含むDLLを公開する場合は、

    6.コンパイラのバージョンと全ての必須なコンパイルオプションも指定し、
     共有DLLでMFCを使用することを条件とする。

    としてます。比較的小規模のものばかりなのでなんとかなってます。

    2013年11月29日 9:39
  • ご返信、ありがとうございます。参考にさせて頂きます。

    1つ不明な点があるので、質問させて下さい。

    > 3.classをexportしない。

    これは、どういった理由からでしょうか。
    アプリ側でメモリ管理(インスタンス生成、解放)をさせたくないとか、そういった理由からでしょうか?

    2013年12月2日 23:54
  • アプリ側でメモリ管理(インスタンス生成、解放)をさせたくないとか、そういった理由からでしょうか?

    状況次第ではありますが、一般的にはアプリ側で管理させたいところです。

    実際、今回のエラーは

    • double-free; 2回解放した
    • 確保したruntimeと異なるruntimeが解放した

    どちらかだと思います。(もしくはもっと致命的な警告メッセージ通りの、全く関係ないアドレスを解放した、もありますが。)

    どちらにしても管理できていないわけです。しかも今回のアプローチ「静的リンク」は、runtimeが別になることを更に強く強制するもので筋が悪いです。
    最初に指摘しましたが、どこで確保され、どこで解放されているのかをきちんと把握すべきです。(ライブラリ製品としては、アプリ側にどう動作させるかコントロールするところまで。)

    例えば、件のクラスのコンストラクタ・デストラクタを共にヘッダファイル内のインライン関数としてあれば、そもそもDLL内にどちらの関数も生成されず、呼び出し側のruntimeで実行させることができます。

    # 仲澤@失業者さんの意見はこれとは反対なようで、要するにC++を使うなに近いかな…?

    2013年12月3日 1:21
  • >> 3.classをexportしない。
    >これは、どういった理由からでしょうか。

    多分に政策的な問題となります。classをエクスポートすると、

     A.ユーザーがそれを派生させたクラスを作成するのを禁止できません。
     B.暗黙に定義されてしまうコンストラクタ、オペレータに配慮が必要です。
     C.そのクラスをDLL内で派生により機能変更する手法が使いづらくなります。
     D.クラス全体のドキュメントの公開が必須となります。

    つまり高コストなわりに危険になるわけです。
    非常に良くできたコンパクトなクラスの場合は問題の発生はほぼ無いと断定できますが、
    比較的まとまった機能であるにも関わらず、その規模が巨大であるクラスに問題が
    発生した場合、該当クラスの修正を行うことがはばかられる場合があります。
    懸念するのは、このケースで、対応の選択肢は、

     1.元のクラスを修正
     2.派生先で迂回方法を実装

    の2つの手段が考えられます。
    ユーザーが2.の方法をとった場合、問題を根本的に解決できないまま
    サポートし続けなければならない場合が考えられます
    (異なった多くのユーザーがいる場合など)。

    この事態を事前に回避する消極的な方法として、条件に該当する
    クラスをエクスポートしないという手段をとる場合があります。
    主にDLLの主機能、統括機能に相当する部分がこれにあたり、
    軽量の補助的クラスはこれに該当しません。
    メモリーの問題は、それよりもっと根本的な部類の手法にあたります。

    まぁ、改造夜露死苦でなくてファミリーセダンの提供ををめざすといったところでしょうか。

    2013年12月3日 2:02