11

我有一些在大型系统中崩溃的代码。但是,代码本质上归结为以下伪代码。我已经删除了很多细节,因为我试图将其归结为裸露的骨头;我不认为这会遗漏任何重要的东西。

// in a DLL:

#ifdef _DLL
#define DLLEXP __declspec(dllexport)
#else
#define DLLEXP __declspec(dllimport)
#endif

class DLLEXP MyClass // base class; virtual
{
public:
  MyClass() {};
  virtual ~MyClass() {};

  some_method () = 0; // pure virtual

  // no member data
};

class DLLEXP MyClassImp : public MyClass
{
public:
  MyClassImp( some_parameters )
  { 
    // some assignments...
  }

  virtual ~MyClassImp() {};

private:
  // some member data...
};

和:

// in the EXE:

MyClassImp* myObj = new MyClassImp ( some_arguments ); // scalar new
// ... and literally next (as part of my cutting-down)...
delete myObj; // scalar delete

请注意,正在使用匹配的标量 new 和标量 delete。

在 Visual Studio (2008 Pro) 的调试版本中,在 Microsoft 的 <dbgheap.c> 中,以下断言失败:

_ASSERTE(_CrtIsValidHeapPointer(pUserData));

靠近堆栈顶部的是以下项目:

mydll_d.dll!operator delete()
mydll_d.dll!MyClassImp::`vector deleting destructor'()

我认为这应该是

mydll_d.dll!MyClassImp::`scalar deleting destructor'()

也就是说,程序的行为就像我写的一样

MyClassImp* myObj = new MyClassImp ( some_arguments );
delete[] newObj; // array delete

中的地址pUserData是它myObj自己的地址(而不是成员)。该地址周围的内存如下所示:

                                ... FD FD FD FD
(address here)
VV VV VV VV MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
FD FD FD FD AB AB AB AB AB AB AB AB EE FE EE FE
...

其中四个VVs 可能是虚函数表的地址,MM...MM是可识别的成员数据,其他字节是调试器放置的各种特殊标记(例如,FD FDs 是对象存储周围的“保护字节”)。

在断言失败前不久,我确实看到了VVs 的变化,并想知道这是否是由于切换到基类的虚函数表所致。

我知道类层次结构中错误级别的问题正在被破坏。这不是这里的问题。我的析构函数都是虚拟的。

我注意到微软的页面“BUG: Wrong Operator Delete Called for Exported Class” http://support.microsoft.com/kb/122675 但这似乎是关于错误的可执行文件(带有错误的堆)试图承担破坏的责任数据。

就我而言,删除析构函数的错误“风格”似乎正在被应用:即向量而不是标量。

我正在尝试生成仍然存在问题的最小缩减代码。

但是,任何有助于如何进一步调查此问题的提示或技巧将不胜感激。

也许这里最大的线索是mydll_d.dll!operator delete()堆栈上的。我应该期望这是myexe_d.exe!operator delete(),表明DLLEXPs 已经“丢失”了吗?

我想这可能是双重删除的一个实例(但我不这么认为)。

关于_CrtIsValidHeapPointer检查的内容,我可以阅读一个很好的参考吗?

4

5 回答 5

8

听起来这可能是从一个堆分配并尝试在另一个堆上删除的问题。当从 dll 分配对象时,这可能是一个问题,因为 dll 有自己的堆。从您展示的代码来看,这似乎不是问题所在,但在简化过程中可能丢失了一些东西?在过去,我看到像这样的代码destroy在对象上使用工厂函数和虚拟方法来确保分配和删除发生在 dll 代码中。

于 2010-07-30T16:07:19.673 回答
1

Microsoft 为其 C 运行时提供源代码;你可以在那里检查看看有什么_CrtIsValidHeapPointer作用。在我的安装中,它位于C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\dbgheap.c.

另一个建议是检查拆卸

delete newObj; // scalar delete

并将其与生成的反汇编进行比较

delete[] newObj;

delete pointerToClassLikeMyClassThatIsInExeAndNotDll;

测试你关于delete[]被召唤的理论。同样,您可以检查调用堆栈

delete pointerToClassLikeMyClassThatIsInExeAndNotDll;

测试你关于mydll_d.dll!operator delete()vs的理论myexe_d.exe!operator delete()

于 2010-07-30T16:05:54.590 回答
1

感谢您的所有回答和评论。所有这些都是有用的和相关的。

任何进一步的信息仍然是受欢迎的。


以下是 Hans Passant 对我的问题的评论:

一旦您开始从 DLL 导出类,使用 /MD 进行编译就变得非常重要。在我看来像 /MT。

因此,我仔细研究了整个项目的链接设置。我发现 /MT 和 /MTd 的“隐藏”实例应该是 /MD 和 /MDd,以及其他设置中的一些相关不一致。

更正了这些,现在没有任何断言被抛出,并且代码似乎表现正确。


以下是在执行离开作用域和调用析构函数时遇到崩溃或断言失败时要检查的一些事项。确保在所有项目(包括依赖项)和所有配置中(尤其是在有问题的配置中):

(这里的 *.vcproj 路径是相对于 </VisualStudioProject/Configurations/Configuration/> 的。)

  • 在 C/C++ 中选择了正确的运行时 | 代码生成 | 运行时库 <Tool[@Name="VCCLCompilerTool"]/@RuntimeLibrary>;
  • 适当的定义(如果有的话)在 C/C++ | 预处理器 | 预处理器定义 <Tool[@Name="VCCLCompilerTool"]/@PreprocessorDefinitions>,尤其是与静态库动态库的使用相关(例如,_STLP_USE_STATIC_LIB 与 STLport 的 _STLP_USE_DYNAMIC_LIB);
  • 在 Linker | 中选择了适当版本的库。输入 | 附加依赖项 <Tool[@Name="VCLinkerTool"]/@AdditionalDependencies> 特别是与静态运行时库DLL 的“包装器”相关(例如 stlport_static.lib 与 stlport.NM .lib)。

有趣的是,我期望的 delete 的标量“风味”似乎仍然没有被调用(断点从未被命中)。也就是说,我仍然只看到向量删除析构函数。因此,这可能是一个“红鲱鱼”。

也许这只是一个 Microsoft 实施问题,或者我还遗漏了其他一些微妙之处。

于 2010-08-03T09:43:25.103 回答
1

此行为对于 MSVC 9 是特殊的,其中隐式生成具有虚拟析构函数的导出类的删除运算符,并使用关联标志将其修改为向量 dtor,其中 1 表示(标量),3 表示(向量)。

这个东西的真正问题是,它打破了新/删除的规范形式,客户端编码器无法在其代码中禁用向量删除运算符,如果他认为使用它是一个坏主意。

此外,向量 dtor 似乎也执行错误,如果 new 分配在另一个模块中,而不是实现所在的模块,然后通过引用计数保存在静态变量中,该引用计数执行删除 this(这里是向量 dtor开始发挥作用)在进程关闭时。

这与前面已经提到的堆问题“bshields”相匹配,dtor 在错误的堆上执行,并且代码在关闭时出现“无法读取该内存位置”或“访问冲突”崩溃——这些问题似乎很常见。

解决此错误的唯一方法是禁止使用虚拟析构函数并自己执行它,方法是强制使用基类中的 delete_this 函数 - 愚蠢,因为你模仿了虚拟 dtor 应该为你做的事情. 然后标量 dtor 也被执行,并且在关闭时,模块之间共享的任何引用计数对象都可以以安全的方式实例化,因为堆总是正确地寻址到源模块。

要检查,如果你有这样的问题,简单地禁止使用向量删除操作符。

于 2011-01-21T23:16:01.190 回答
0

就我而言,删除析构函数的错误“风格”似乎正在被应用:即向量而不是标量。

这不是这里的问题。根据Mismatching scalar and vector new and delete中的伪代码,scalar deleting destructor简单地调用vector deleting descructor带有“做标量破坏而不是矢量破坏”的标志。

正如其他海报所指出的,您的实际问题是您在一个堆上分配,而在另一个堆上删除。最明确的解决方案是让你的类重载operator newand operator delete,正如我在对类似问题的回答中所描述的那样:Error delete std::vector in a DLL using the PIMPL idiom

于 2016-02-06T04:17:22.437 回答