我有一些代码在大型系统中崩溃。 但是,代码基本上归结为以下伪代码。 我已经删除了很多细节,因为我试图把它煮到骨头上; 我不认为这会错过任何重要的事情。
// 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
请注意,正在使用匹配标量新标量和标量删除。
在Visual Studio(2008 Pro)的Debug构建中, 在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
...
其中四个VV
可能是虚函数表的地址,
MM...MM
是可识别的成员数据,
其他字节是调试器放置的各种特殊标记
(例如,FD FD
s是对象存储周围的“保护字节”。
断言失败前不久我确实看到了VV
的变化,
并且想知道这是否是由于切换到基类的虚函数表。
我知道正在进行破坏的类层次结构中的错误级别问题。 这不是问题所在;我的析构函数都是虚拟的。
我注意到微软的页面 “BUG:错误的操作员删除调用导出的类” http://support.microsoft.com/kb/122675 但这似乎是关于错误的可执行文件(错误的堆)试图承担破坏数据的责任。
就我而言,删除析构函数的错误“味道”似乎正在应用: 即向量而不是标量。
我正在尝试制作仍然存在问题的最小缩减代码。
但是,任何有助于如何进一步调查此问题的提示或提示都将非常感激。
也许这里最大的线索是堆栈上的mydll_d.dll!operator delete()
。
我应该期望这是myexe_d.exe!operator delete()
,
表明DLLEXP
已被“丢失”?
我想这可能是双删除的一个实例(但我不这么认为)。
我可以阅读一篇关于_CrtIsValidHeapPointer
检查的内容的好参考资料吗?
答案 0 :(得分:8)
听起来这可能是分配一个堆并尝试删除另一个堆的问题。从dll分配对象时,这可能是一个问题,因为dll有自己的堆。从您显示的代码看起来似乎不是这个问题,但可能在简化中丢失了一些东西?在过去,我看到这样的代码在对象上使用工厂函数和虚拟destroy
方法,以确保在dll代码中发生分配和删除。
答案 1 :(得分: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()
与myexe_d.exe!operator delete()
的理论。
答案 2 :(得分:1)
感谢您的所有答案和评论。 所有这些都是有用和相关的。
仍然欢迎任何进一步的信息。
以下是Hans Passant对我提问的评论:
从DLL开始导出类后, 用/ MD编译变得非常重要。 看起来像/ MT给我。
因此,我仔细研究了整个项目中的联动设置。 我找到了/ MT和/ MTd的“埋藏”实例应该是/ MD和/ MDd, 加上其他设置中的一些相关不一致。
纠正了这些, 现在没有抛出任何断言,代码似乎表现正常。
以下是在执行时遇到崩溃或断言失败以及调用析构函数时要检查的一些事项。 确保所有项目(包括依赖项) 并且在所有配置中(特别是在有问题的配置中):
(这里* .vcproj路径相对于< / VisualStudioProject / Configurations / Configuration /&gt ;.)
有趣的是,我希望删除的标量'风味'似乎仍然没有被调用(断点永远不会被击中)。 也就是说,我仍然只看到向量删除析构函数。 因此,这可能是一个'红鲱鱼'。
也许这只是微软的实施问题, 或许还有一些我错过的其他微妙之处。
答案 3 :(得分:1)
此行为对于MSVC 9来说是特殊的,其中具有虚拟析构函数的导出类的delete运算符被隐式生成并被修改为带有关联标志的向量dtor,其中1表示(标量),3表示(向量)
这件事的真正问题在于它打破了new / delete的规范形式,其中客户端编码器无法在其代码中禁用向量删除运算符,如果他认为,这是一个坏主意用它。
此外,矢量dtor似乎执行错误,如果new在另一个模块中分配,而不是实现所在的模块,然后通过引用计数保存在静态变量中,执行删除(这里)过程关闭时,矢量dtor开始发挥作用。
这与之前已经提到的堆问题“bshields”匹配,dtor在错误的堆上执行,代码在“关闭时无法读取内存位置”或“访问冲突”时出现问题 - 这些问题似乎是很常见。
解决这个错误的唯一方法是禁止使用虚拟析构函数并自己执行它,通过强制使用基类中的delete_this函数 - 愚蠢,因为它模仿虚拟dtor应该的东西为你做然后标量dtor也会被执行,并且在关闭时,模块之间共享的任何ref计数对象都可以以安全的方式实例化,因为堆总是正确地寻址到原始模块。
要检查,如果您遇到此类问题,请禁止使用矢量删除操作符。
答案 4 :(得分:0)
就我而言,这是删除析构函数的错误“味道” 似乎正在应用:即向量而不是标量。
这不是问题所在。根据{{3}}中的伪代码,scalar deleting destructor
只是通过标记“标量破坏而非矢量破坏”来调用vector deleting descructor
。
正如其他海报所指出的,你的实际问题是你在一个堆上分配,在另一个堆上删除。最清楚的解决方案是让您的类超载Mismatching scalar and vector new and delete和operator new
,正如我在回答类似问题时所描述的那样:operator delete