web-dev-qa-db-ja.com

リリースビルドがデバッグビルドとは異なる動作をするいくつかの理由は何ですか

リリースモードでの実行とデバッグモードでの実行が異なるVisual Studio 2005 C++プログラムがあります。リリースモードでは、(見かけ上の)断続的なクラッシュが発生します。デバッグモードでは、クラッシュしません。リリースビルドがデバッグビルドとは異なる動作をする理由は何ですか?

また、私のプログラムはかなり複雑で、XML処理、メッセージブローカリングなどのためにいくつかのサードパーティライブラリを使用していることにも言及する価値があります。

前もって感謝します!

54
BeachRunnerFred

Surviving the Release Version は、適切な概要を提供します。

私が遭遇したこと-ほとんどはすでに言及されています

変数の初期化が最も一般的です。 Visual Studioでは、デバッグビルドは割り当てられたメモリを明示的に初期化して、指定された値に設定します。 メモリ値 ここ。これらの値は通常簡単に特定でき、インデックスとして使用すると境界外エラーが発生し、ポインタとして使用するとアクセス違反が発生します。ただし、初期化されていないブール値はtrueであり、初期化されていないメモリバグが何年も検出されないままになる可能性があります。

メモリが明示的に初期化されていないリリースビルドでは、以前の内容を保持するだけです。これは、「おかしな値」と「ランダムな」クラッシュを引き起こしますが、実際にはクラッシュするコマンドの前に、明らかに無関係なコマンドを実行する必要がある決定論的なクラッシュをしばしば引き起こします。これは、特定の値でメモリロケーションを「設定」する最初のコマンドが原因で発生します。メモリロケーションがリサイクルされると、2番目のコマンドはそれらを初期化として認識します。これは、ヒープよりも初期化されていないスタック変数の方が一般的ですが、後者も起こりました。

生のメモリの初期化は、Visual Studio(デバッガーが接続されている)から開始するか、エクスプローラーから開始するかに関係なく、リリースビルドで異なる場合があります。これにより、デバッガーの下には決して表示されない「最も良い」種類のリリースビルドバグが作成されます。

有効な最適化は、私の例では2番目です。 C++標準では、多くの最適化を行うことができますが、これは驚くべきことかもしれませんが、完全に有効です。 2つのポインターが同じメモリロケーションをエイリアスする場合、初期化の順序は考慮されないか、複数のスレッドが同じメモリロケーションを変更し、スレッドBがスレッドAによって行われた変更を参照する特定の順序を予期します。多くの場合、コンパイラーのせいですこれら。それほど速くはない、若いイェディ! - 下記参照

タイミングリリースビルドは、さまざまな理由(最適化、スレッド同期ポイントを提供するロギング関数、アサートなどのデバッグコード)のために、単に「より速く」実行されるだけではありません。実行されないなど)また、操作間の相対的なタイミングは劇的に変化します。これによって明らかになった最も一般的な問題は、競合状態ですが、デッドロックと、メッセージ/タイマー/イベントベースのコードの単純な「異なる順序」の実行です。それらはタイミングの問題ですが、canビルドとプラットフォーム全体で驚くほど安定しており、「PC 23を除いて常に機能します」という再現があります。

Guard Bytes。デバッグビルドでは、インデックスのオーバーフローおよび場合によってはアンダーフローから保護するために、選択されたインスタンスと割り当ての周囲に(より多くの)ガードバイトを配置することがよくあります。コードがオフセットまたはサイズに依存するまれなケースでは、生の構造をシリアル化すると、それらは異なります。

その他のコードの違い一部の命令-アサートなど-は、リリースビルドでは何も評価しません。時々彼らは異なる副作用を持っています。これは、古典的なようにマクロの策略で蔓延しています(警告:複数のエラー)

#ifdef DEBUG
#define Log(x) cout << #x << x << "\n";
#else 
#define Log(x)
#endif

if (foo)
  Log(x)
if (bar)
  Run();

これは、リリースビルドでif(foo && bar)と評価されます。このタイプのエラーは、通常のC/C++コード、および正しく記述されたマクロでは非常にまれです。

コンパイラのバグこれは実際に発生することはありません。まあ-それはそうですが、あなたのキャリアの大部分は、そうでないと仮定した方が良いです。 VC6での作業の10年の間に、私はこれが未修正のコンパイラーのバグであると確信しているものを見つけました。

125
peterchen

デバッグバージョンでは、アサーションやデバッグシンボルが有効になっていることがよくあります。これにより、メモリレイアウトが異なる可能性があります。不正なポインター、配列のオーバーフロー、またはアクセスする同様のメモリーアクセスの場合、重大な不良メモリー(関数ポインターなど)と、重要ではない一部のメモリー(たとえば、ドキュメント文字列だけが破棄される)でアクセスする

6
flolo

明示的に初期化されていない変数は、リリースビルドでゼロ化される場合とゼロ化されない場合があります。

5
Burkhard

リリースビルドは(できれば)デバッグビルドよりも高速に実行されます。複数のスレッドを使用している場合、インターリーブが多くなるか、1つのスレッドが他のスレッドよりも高速に実行される可能性があります。これは、デバッグビルドでは気付かなかった可能性があります。

2
Eugene Yokota

通常、リリースビルドはコンパイラーで最適化を有効にしてコンパイルされますが、デバッグビルドは通常はコンパイルされません。

一部の言語で、または多くの異なるライブラリを使用している場合、これは断続的なクラッシュを引き起こす可能性があります-特に選択した最適化レベルが非常に高い場合。

これがgcc C++コンパイラの場合であることは知っていますが、Microsoftのコンパイラについてはわかりません。

2
fluffels

私は かなり前に同様の問題 を抱えていましたが、これは、リリースビルドでスタックの扱いが異なることが原因でした。異なる可能性のある他のこと:

  • メモリの割り当ては、VSコンパイラのデバッグビルドでは異なる方法で処理されます(つまり、クリアされたメモリに0xccを書き込むなど)。
  • ループの展開とその他のコンパイラの最適化
  • ポインタの悟り
2
Nik Reiman

これは、コンパイラベンダーと、DEBUGフラグを使用してコンパイルするライブラリの両方に依存します。 DEBUGコードが実行中のコードに影響を与えることは決してありませんが(副作用がないはずです)、時々影響を及ぼします。

特に、変数はDEBUGモードでのみ初期化され、RELEASEモードでは初期化されないままになります。 Visual StudioコンパイラのSTLは、DEBUGモードとRELEASEモードで異なります。考えられるエラーは、イテレータがDEBUGで完全にチェックされることです(無効化されたイテレータを使用します。たとえば、イテレータが取得された後に挿入が行われた場合、ベクトルへのイテレータは無効になります)。

同じことがサードパーティのライブラリでも起こります。最初に思いつくのはQT4です。グラフィックオブジェクトを作成したスレッドとは異なるスレッドがペイント操作を実行すると、プログラムがアサートで終了します。

すべての変更により、コードとメモリのフットプリントは両方のモードで異なります。ポインタ(配列の最後を通過する位置を読み取る)の問題は、その位置が読み取り可能である場合、検出されずに通過することがあります。

アサーションは、デバッグ中にアプリケーションを強制終了し、リリースビルドから消えることを目的としているため、アサーションを問題とは考えていません。不正なポインタまたは最後を過ぎたものへのアクセスは、私の最初の容疑者でしょう。

少し前に、コードを壊すコンパイラの最適化に問題がありましたが、最近、苦情を読んでいません。そこに最適化の問題があるかもしれませんが、これは私の最初の疑いではありません。

http://www.debuginfo.com/tips/userbpntdll.html

デバッグビルドでガードバイトが追加されるため、配列(特に動的配列)の範囲外のメモリに「安全に」アクセスできる場合がありますが、これにより、リリースビルドでアクセス違反が発生します。 。このエラーは気付かれない可能性があり、破損したヒープを引き起こし、おそらく元のエラーとは無関係の場所でアクセス違反を引き起こします。

PageHeap(またはデバッグツールがインストールされている場合はgflagsを使用できます)を使用して、破損したヒープに関連するバグを発見します。

http://support.Microsoft.com/?id=28647

1
DCX2

私の経験では、最も一般的な理由は、構成がリリース/ビルド設定よりも多くの点で異なるということです。例えば。異なるライブラリが含まれているか、デバッグビルドのスタックサイズがリリースビルドと異なります。

Visual Studio 2005プロジェクトでこれを回避するには、プロパティシートを広範囲に使用します。このようにして、リリースとデバッグの構成で共通の設定を共有できます。

0
Tobias Furuholm

リリースモードで実行できる(デバッグでは実行できない)最適化の中で、コピーの省略は異なる結果をもたらす可能性があります。より具体的には、コンストラクターの設計方法に応じて、RVO(戻り値の最適化)。

コピーの省略と戻り値の最適化とは

0
brahmin

この投稿と提供されたリンクは、関連するバグの修正に非常に役立ちます。上記のリストに加えて、呼び出し規約の違いもこの動作につながる可能性があります-私のための最適化のみでリリースビルドに失敗しました。 __stdcallとして宣言し、__ cdeclとして定義しました(デフォルト)。 [奇妙なことに、この警告は警告レベル4 MSVCでも選択されないのですか?]

0
FL4SOF