C ++ Builder 2010奇怪的访问冲突

时间:2012-11-15 10:10:12

标签: c++builder c++builder-2010

我已经有了一个程序,它将成为使用C ++ Builder 2010构建的现有大型产品的一部分。

较小的程序(尚未)依赖于C ++ Builder。它在MS Visual Studio中工作正常,但是使用C ++ Builder会产生奇怪的访问冲突。

请让我解释一下。

根据代码和编译器设置,发生或不发生访问冲突。访问冲突是可重现的:当构建程序时,访问冲突永远不会发生,或者它始终发生在同一个地方。如果使用相同的设置重建程序,它将显示相同的行为。 (我真的很高兴)。

访问冲突发生在调用delete运算符的位置。这可能发生在某些析构函数中(取决于编译器设置和精确代码),包括自己类的析构函数和std :: string的析构函数。

以下情况会降低访问冲突的可能性:

  • 使用“调试”设置构建(而不是“发布”)。
  • 没有编译器优化。
  • 编译器切换“Slow exception epilogues”。
  • 静态RTL而非动态。
  • 从std :: exception中派生异常,而不是Borland的Exception类。
  • 使用较少的“复杂”表达式(例如,使用“string s =”......“+”......“;抛出SomeException; s”而不是“throw” SomeException(string(“...”)+“...”);“)
  • 使用try ... __finally with manual cleanup而不是使用析构函数自动变量。
  • 使用小型控制台应用程序代替VCL Windows应用程序。

该程序使用了几个C ++特性,包括异常,STL,移动构造函数等,当然它使用堆。

我已经尝试了一些工具,但没有一个报告过问题:

  • Borland的CodeGuard。
  • Microsoft Application Verifyer。
  • 页堆/ GFLAGS。
  • 如前所述,使用MS Visual Studio构建时绝对没有问题。

使用预编译的标头和增量链接(我认为这两者都容易出错)。

C ++ Builder编译器(“启用所有警告”)和Visual Studio(/ W4)之一都不会产生可能与此问题相关的警告。

我无法访问其他版本的C ++ Builder。

由于程序将成为更大产品的一部分,因此不能选择切换到不同的编译器,并且在不再发生访问冲突之前,不能选择调整编译器设置。 (我担心如果真的应该编译错误,那么错误可能会再次出现。)

把它放在一起,我猜这可能是由于与某些编译器错误有关的堆损坏造成的。但是,我无法在qc.embarcadero.com上找到错误。我进一步猜测这与在抛出异常时在堆栈倒带时执行的清理代码有关。但是,好吧,也许这只是一个愚蠢的代码错误。

目前,我不知道如何继续。任何帮助赞赏。提前谢谢!

2 个答案:

答案 0 :(得分:2)

tl; dr 我认为错误是生成代码以在堆栈展开期间从三元运算符的两个分支中删除std::string,但实际上只创建了其中一个当然。


这是一个更简单的MCVE,它通过XE5中的输出显示问题:

#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的版本并改为导致段错误。

我的输出是:

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

在原始代码中,segfault来自伪造的Destroy,最终通过尝试检索实际上从未创建的delete的{​​{1}}的内部数据指针来获取伪造的值std::string那个位置。

我的猜想是,堆栈展开的代码生成被窃听,并尝试从三元运算符的两个分支中删除临时字符串。临时UnicodeString的存在确实与它有关;因为在我试图避免那种临时性的任何变化中没有发生错误。

在调试器中,您可以看到调用堆栈,并且在全局堆栈展开期间发生这种情况。

答案 1 :(得分:0)

Phew,这很简单,花了我一些时间:

#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 ++ Builder 2010和XE3试用版崩溃。

因此,编译器或VCL或STL中的错误或其他。