トップ回答者
vectorのデストラクタについて

質問
-
vector のメンバ変数を持つクラスを、new してインスタンス生成。
その後、delete すると「ヒープが壊れていることが原因として考えられます。」という警告が表示されます。vector を含んだクラスはVS2002 C++でビルドしたDLLファイルです。
アプリは、VS2012 C++です。アプリがVS2010 C++の場合、この問題は発生しません。
●知りたいこと
・VS2012 C++のアプリで、この警告が出ないようにする方法。
・この警告が表示される原因。特にVS2012から vectorに関するデストラクタで何か変更があったのでしょうか?
回答
-
佐祐理さんが既に指摘していますが、やや補足すると、
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 配列等への範囲外アクセス。などの点を確認してみてはどうでしょう。
-
> 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
すべての返信
-
佐祐理さんが既に指摘していますが、やや補足すると、
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 配列等への範囲外アクセス。などの点を確認してみてはどうでしょう。
-
情報が不十分でした。
VS2002 C++でビルドしたDLLファイルは、MFC 共有 DLLです。
そのため、VS2012 C++のアプリ側でメモリ管理が行われていました。vectorは、クラスのデストラクタが呼ばれたときに自動的に解放されるはずですが、
VS2012 C++ (アプリ側)のランタイムを使用してメモリの解放が行われています。vectorが含まれているクラスだけ発生するのは、VC++ 2012からvectorのサイズが
変更になっていて、それが原因で発生しているような気がします。問題に関しては、解決の目処が立ちました。
助言いただき、有難う御座いました。 -
> 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
-
>それともVSのバージョンに合わせて、ライブラリを提供していく方が良いのでしょうか。
判断できません。公開される製品の性質によると考えられるからです。
個人的には、DLL「だけ」を外部に提供する場合、次のような方針でやってます。
以下、全て「可能であれば」の条件付きですが、1.CRTとWin32SDKのみで構築する。
2.公開するヘッダーにコードを含めない。
開発用と公開用のヘッダーは別の場合もありえる。
3.classをexportしない。
4.場合によっては、C言語インターフェースのみ使用する。
5.場合によっては、defによるEXPORTSを使用する。やむを得ずMFCのコードを含むDLLを公開する場合は、
6.コンパイラのバージョンと全ての必須なコンパイルオプションも指定し、
共有DLLでMFCを使用することを条件とする。としてます。比較的小規模のものばかりなのでなんとかなってます。
- 編集済み 仲澤@失業者 2013年11月29日 9:40
-
アプリ側でメモリ管理(インスタンス生成、解放)をさせたくないとか、そういった理由からでしょうか?
状況次第ではありますが、一般的にはアプリ側で管理させたいところです。
実際、今回のエラーは
- double-free; 2回解放した
- 確保したruntimeと異なるruntimeが解放した
どちらかだと思います。(もしくはもっと致命的な警告メッセージ通りの、全く関係ないアドレスを解放した、もありますが。)
どちらにしても管理できていないわけです。しかも今回のアプローチ「静的リンク」は、runtimeが別になることを更に強く強制するもので筋が悪いです。
最初に指摘しましたが、どこで確保され、どこで解放されているのかをきちんと把握すべきです。(ライブラリ製品としては、アプリ側にどう動作させるかコントロールするところまで。)例えば、件のクラスのコンストラクタ・デストラクタを共にヘッダファイル内のインライン関数としてあれば、そもそもDLL内にどちらの関数も生成されず、呼び出し側のruntimeで実行させることができます。
# 仲澤@失業者さんの意見はこれとは反対なようで、要するにC++を使うなに近いかな…?
-
>> 3.classをexportしない。
>これは、どういった理由からでしょうか。多分に政策的な問題となります。classをエクスポートすると、
A.ユーザーがそれを派生させたクラスを作成するのを禁止できません。
B.暗黙に定義されてしまうコンストラクタ、オペレータに配慮が必要です。
C.そのクラスをDLL内で派生により機能変更する手法が使いづらくなります。
D.クラス全体のドキュメントの公開が必須となります。つまり高コストなわりに危険になるわけです。
非常に良くできたコンパクトなクラスの場合は問題の発生はほぼ無いと断定できますが、
比較的まとまった機能であるにも関わらず、その規模が巨大であるクラスに問題が
発生した場合、該当クラスの修正を行うことがはばかられる場合があります。
懸念するのは、このケースで、対応の選択肢は、1.元のクラスを修正
2.派生先で迂回方法を実装の2つの手段が考えられます。
ユーザーが2.の方法をとった場合、問題を根本的に解決できないまま
サポートし続けなければならない場合が考えられます
(異なった多くのユーザーがいる場合など)。この事態を事前に回避する消極的な方法として、条件に該当する
クラスをエクスポートしないという手段をとる場合があります。
主にDLLの主機能、統括機能に相当する部分がこれにあたり、
軽量の補助的クラスはこれに該当しません。
メモリーの問題は、それよりもっと根本的な部類の手法にあたります。
まぁ、改造夜露死苦でなくてファミリーセダンの提供ををめざすといったところでしょうか。- 編集済み 仲澤@失業者 2013年12月3日 2:35