2

我有一个程序将成为使用 C++ Builder 2010 构建的现有更大产品的一部分。

较小的程序(还)不依赖于 C++ Builder。它在 MS Visual Studio 中运行良好,但使用 C++ Builder 会产生奇怪的访问冲突。

请让我解释一下。

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

访问冲突发生在调用删除运算符的地方。这可能发生在某些析构函数内部(取决于编译器设置和确切的代码),包括自身类的析构函数和 std::string 的析构函数内部。

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

  • 使用“调试”设置(而不是“发布”)构建。
  • 没有编译器优化。
  • 编译器开关“慢速异常尾声”。
  • 静态 RTL 而不是动态。
  • 从 std::exception 而不是 Borland 的 Exception 类派生异常。
  • 使用较少的“复杂”表达式(例如,使用“string s = “...” + “...”; throw SomeException(s);" 而不是 "throw SomeException(string("...") + ".. .");")
  • 使用 try... __finally 进行手动清理,而不是使用带有析构函数的自动变量。
  • 使用小型控制台应用程序而不是 VCL Windows 应用程序。

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

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

  • Borland 的 CodeGuard。
  • Microsoft 应用程序验证器。
  • 页堆/gflags。
  • 如前所述,使用 MS Visual Studio 构建绝对没有问题。

预编译头文件和增量链接(在我看来都容易出错)的使用被禁用。

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

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

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

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

目前,我不知道如何进行。任何帮助表示赞赏。先感谢您!

4

2 回答 2

2

tl;博士我相信这个错误是生成代码以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

在原始代码中,段错误来自伪造的 Destroy 最终调用delete它通过尝试检索std::string实际上从未在该位置创建的 a 的内部数据指针而获得的伪造值。

我的猜测是堆栈展开的代码生成存在错误,并试图从三元运算符的两个分支中删除临时字符串。临时的存在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++ Builder 2010 和 XE3 试用版崩溃。

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

于 2012-11-19T14:01:53.667 回答