2

C ++Builder2010を使用して構築された既存のより大きな製品の一部となるプログラムがあります。

小さいプログラムは(まだ)C++Builderに依存していません。MS Visual Studioでは正常に動作しますが、C ++ Builderを使用すると、奇妙なアクセス違反が発生します。

これを説明させてください。

コードとコンパイラの設定に応じて、アクセス違反が発生する場合と発生しない場合があります。アクセス違反は再現可能です。プログラムがビルドされると、アクセス違反は発生しないか、常に同じ場所で発生します。プログラムを同じ設定で再構築すると、同じ動作を示します。(私はそれについて本当にうれしいです)。

アクセス違反は、削除演算子が呼び出された場所で発生します。これは、(コンパイラの設定と正確なコードに応じて)特定のデストラクタ内(独自のクラスのデストラクタを含む)およびstd::stringのデストラクタ内で発生する可能性があります。

次のことにより、アクセス違反の可能性が低くなります。

  • (「リリース」ではなく)「デバッグ」設定でビルドします。
  • コンパイラの最適化はありません。
  • コンパイラスイッチ「遅い例外エピローグ」。
  • 動的ではなく静的RTL。
  • BorlandのExceptionクラスではなく、std::exceptionから例外を取得します。
  • あまり「複雑でない」式を使用します(たとえば、「throw SomeException(string( "...")+」の代わりに「string s = "..." + "..."; throw SomeException(s);」を使用します。 。");")
  • デストラクタを使用した自動変数の代わりに、手動クリーンアップを使用してtry...__finallyを使用します。
  • VCLWindowsアプリケーションの代わりに小さなコンソールアプリケーションを使用してください。

このプログラムは、例外、STL、移動コンストラクターなどを含むいくつかのC ++機能を利用し、もちろんヒープを使用します。

私はすでにいくつかのツールを試しましたが、どれも問題を報告しませんでした:

  • ボーランドのCodeGuard。
  • Microsoftアプリケーション検証ツール。
  • pageheap/gflags。
  • すでに述べたように、MSVisualStudioでビルドする場合はまったく問題ありません。

プリコンパイル済みヘッダーとインクリメンタルリンク(どちらもエラーが発生しやすいようです)の使用は無効になっています。

C ++ Builderコンパイラ(「すべての警告を有効にする」)もVisual Studio(/ W4)のコンパイラも、この問題に関連する可能性のある警告を生成しません。

別のバージョンのC++Builderにアクセスできません。

プログラムはより大きな製品の一部になるため、別のコンパイラに切り替えるオプションはなく、アクセス違反が発生しなくなるまでコンパイラ設定を調整するオプションもありません。(これが本当にコンパイラのバグである場合、バグが再び現れる可能性があるのではないかと心配しています。)

これをまとめると、これはコンパイラのバグに関連するヒープの破損が原因である可能性があると思います。ただし、qc.embarcadero.comでバグを見つけることができませんでした。さらに、これは、例外がスローされたときにスタックの巻き戻し時に実行されるクリーンアップコードに関連していると推測しています。しかし、まあ、それはばかげたコードのバグにすぎないのかもしれません。

現在、どうすればいいのかわかりません。助けていただければ幸いです。前もって感謝します!

4

2 に答える 2

2

tl; drバグは、std::stringスタックの巻き戻し中に三項演算子の両方のブランチからを削除するコードが生成されることだと思いますが、もちろん実際にはそのうちの1つだけが作成されました。


XE5の出力を介して問題を示す、より単純なMCVEを次に示します。

#include <vcl.h>
#include <tchar.h>
#include <stdio.h>
using namespace std;

struct S
{
    S() { printf("Create: %p\n", this); }
    S(S const &) { printf("Copy: %p\n", this); }
    void operator=(S const &) { printf("Assign: %p\n", this); }
    ~S() { printf("Destroy: %p\n", this); }

    char const *c_str() { return "xx"; }
};

S rX() { return S(); }
int foo() { return 2; }

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
   try
   {
      throw Exception( (foo() ? rX() : rX()).c_str() );
   }
   catch (const Exception& e)
   {
   }

   getchar();
   return 0;
}

このバージョンは、コンソールの出力文字列を介して問題を示しています。この投稿の編集履歴をチェックして、std::string代わりにsegfaultを使用して発生させるバージョンを確認してください。

私の出力は次のとおりです。

 Create: 0018FF38
Destroy: 0018FF2C
Destroy: 0018FF38

元のコードでは、セグメンテーション違反は偽のDestroyから発生し、実際にはその場所で作成されなかっdeleteたの内部データポインターを取得しようとして、取得した偽の値を呼び出します。std::string

私の推測では、スタックアンワインドのコード生成にバグがあり、三項演算子の両方のブランチから一時的な文字列を削除しようとしています。一時的な存在はUnicodeStringそれと関係があります。一時的なものを避けようとしたバリエーションではバグは発生しなかったためです。

デバッガーでは、呼び出しスタックを確認できます。これが発生するのは、グローバルスタックの巻き戻し中です。

于 2014-11-25T01:21:26.290 に答える
0

ふぅ、それはとても単純だったので、私は少し時間がかかりました:

#include <vcl.h>
#include <tchar.h>
#include <string>
using namespace std;

struct B
{
   B(const char* c) { }
   string X() const { return "xx"; }
   int Length() const { return 2; }
};

struct C
{
   void ViolateAccess(const B& r)
   {
      try
      {
         throw Exception(string("aoei").c_str());
      }
      catch (const Exception&) { }

      throw Exception(((string) "a" + (r.Length() < 10 ? r.X() : r.X() + "...") + "b").c_str());
   }
};

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
   try
   {
      C c;
      c.ViolateAccess("11");
   }
   catch (const Exception& e) { }
   return 0;
}

(先制コメント:いいえ、このコードは意味がありません。)

新しいコンソールアプリケーションを作成し、必ずVCLを使用してください。アクセス違反があるかどうかは、プロジェクトの設定によって異なる場合があります。私のデバッグビルドは常にクラッシュしましたが、リリースビルドはクラッシュしませんでした。

C ++Builder2010およびXE3トライアルでクラッシュします。

したがって、コンパイラ、VCL、STLなどのバグです。

于 2012-11-19T14:01:53.667 に答える