为什么标量删除会调用向量删除析构函数?

发布于 2024-09-12 00:55:20 字数 2383 浏览 5 评论 0原文

我有一些代码在大型系统中崩溃。 然而,该代码本质上可以归结为以下伪代码。 我删除了很多细节,因为我试图将其归结为最基本的内容; 但我认为这并没有遗漏任何重要的东西。

// 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 和标量删除。

在 Visual Studio (2008 Pro) 的调试版本中, 在微软的中, 以下断言失败:

_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 中的地址是 < code>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是对象存储周围的“保护字节”)。

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

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

我注意到微软的页面 “BUG:导出类调用了错误的运算符删除” http://support.microsoft.com/kb/122675 但这似乎是关于错误的可执行文件(具有错误的堆)试图承担破坏数据的责任。

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

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

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

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

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

关于 _CrtIsValidHeapPointer 检查的内容,是否有好的参考可供阅读?

I have some code that is crashing in a large system.
However, the code essentially boils down to the following pseudo-code.
I've removed much of the detail, as I have tried to boil this down to the bare bones;
I don't think this misses anything crucial though.

// 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...
};

and:

// in the EXE:

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

Note that matching scalar new and scalar delete are being used.

In a Debug build in Visual Studio (2008 Pro),
in Microsoft's <dbgheap.c>,
the following assertion fails:

_ASSERTE(_CrtIsValidHeapPointer(pUserData));

Near the top of the stack are the following items:

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

I think this ought to be

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

That is, the program is behaving as if I'd written

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

The address in pUserData is that of myObj itself (as opposed to a member).
The memory around that address looks like this:

                                ... 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
...

where the four VVs are presumably the address of the virtual function table,
the MM...MM is recognisable member data,
and the other bytes are various special markers put in place by the debugger
(e.g. the FD FDs are 'guard bytes' around the object's storage).

Shortly before the assertion failure I do see the VVs change,
and wonder if that is due to a switch to the base class's virtual function table.

I'm aware of the problem of the wrong level in the class hierarchy undergoing destruction.
That's not the problem here; my destructors are all virtual.

I note Microsoft's page
"BUG: Wrong Operator Delete Called for Exported Class"
http://support.microsoft.com/kb/122675
but that seems to be regarding the wrong executable (with the wrong heap) attempting to take responsibility for destruction of the data.

In my case, it's that the wrong 'flavour' of deleting destructor appears to be being applied:
i.e. vector rather than scalar.

I am in the process of trying to produce minimal cut-down code that still exhibits the problem.

However, any hints or tips to help with how to investigate this problem further would be much appreciated.

Perhaps the biggest clue here is the mydll_d.dll!operator delete() on the stack.
Should I expect this to be myexe_d.exe!operator delete(),
indicating that the DLLEXPs have been 'lost'?

I suppose this could be an instance of a double-delete (but I don't think so).

Is there a good reference I can read regarding what _CrtIsValidHeapPointer checks for?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

匿名的好友 2024-09-19 00:55:21

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

这不是问题所在。根据标量和向量新建和删除不匹配中的伪代码, 标量删除析构函数只需调用向量删除析构函数,并带有一个标志“进行标量破坏而不是向量破坏”。

正如其他发帖者所指出的,您的实际问题是您在一个堆上分配,并在另一堆上删除。最清晰的解决方案是为您的类提供 operator 的重载new运算符删除,正如我在类似问题的回答中所描述的:在 DLL 中删除 std::vector 时出错使用 PIMPL 习惯用法

In my case, it's that the wrong 'flavour' of deleting destructor
appears to be being applied: i.e. vector rather than scalar.

That's not the problem here. As per the pseudocode in Mismatching scalar and vector new and delete, the scalar deleting destructor simply calls through to the vector deleting descructor with a flag saying "Do scalar destruction rather than vector destruction".

Your actual problem, as noted by other posters, is you're allocating on one heap, and deleting on another. The clearest solution is to give your classes overloads of operator new and operator delete, as I described in an answer to a similar question: Error deleting std::vector in a DLL using the PIMPL idiom

¢好甜 2024-09-19 00:55:20

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

Sounds like this could be an issue of allocating off of one heap and trying to delete on another. This can be an issue when allocating objects from a dll as the dll has its own heap. From the code you're showing it doesn't seem like this would be the problem but maybe in the simplification something was lost? In the past I've see code like this use factory functions and virtual destroy methods on the objects to make sure that the allocation and deletion happens in the dll code.

無心 2024-09-19 00:55:20

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() 的理论。

Microsoft provides the source for their C runtime; you can check there to see what _CrtIsValidHeapPointer does. On my installation, it's under C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\dbgheap.c.

One other suggestion is to check the disassembly of

delete newObj; // scalar delete

and compare it to the disassembly generated for

delete[] newObj;

and

delete pointerToClassLikeMyClassThatIsInExeAndNotDll;

to test your theory about delete[] being called. Similarly, you could check the call stack for

delete pointerToClassLikeMyClassThatIsInExeAndNotDll;

to test your theory about mydll_d.dll!operator delete() versus myexe_d.exe!operator delete().

二手情话 2024-09-19 00:55:20

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

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


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

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

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

纠正了这些之后,
现在不会抛出任何断言,并且代码似乎行为正确。


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

此处 *.vcproj 路径相对于 。)

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

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

也许这只是微软的一个实施问题,
或者也许还有一些我错过的其他微妙之处。

Thank you for all the answers and comments.
All have been useful and relevant.

Any further information is still welcome.


The following was a comment on my question from Hans Passant:

Once you start exporting classes from DLLs,
compiling with /MD becomes very important.
Looks like /MT to me.

As a result of that, I took a closer look at the linkage setting throughout the project.
I found a 'buried' instance of /MT and /MTd that should have been /MD and /MDd,
plus some related inconsistencies in other settings.

Having corrected those,
no assertion is now thrown, and the code appears to be behaving correctly.


Here are some of the things to check when experiencing crashes or assertion failures at execution leaves scopes and destructors are called.
Ensure that throughout all projects (including dependencies)
and in all configurations (especially in the problematic one):

(Here the *.vcproj paths are relative to </VisualStudioProject/Configurations/Configuration/>.)

  • The correct runtime is selected in
    C/C++ | Code Generation | Runtime Library
    <Tool[@Name="VCCLCompilerTool"]/@RuntimeLibrary>;
  • Appropriate definitions (if any) are made in
    C/C++ | Preprocessor | Preprocessor Definitions
    <Tool[@Name="VCCLCompilerTool"]/@PreprocessorDefinitions>
    especially relating to the use of static versus dynamic libraries
    (e.g. _STLP_USE_STATIC_LIB versus _STLP_USE_DYNAMIC_LIB for STLport);
  • Appropriate versions of libraries are selected in
    Linker | Input | Additional Dependencies
    <Tool[@Name="VCLinkerTool"]/@AdditionalDependencies>
    especially relating to static runtime libraries versus 'wrappers' for DLLs
    (e.g. stlport_static.lib versus stlport.N.M.lib).

Interestingly, the scalar 'flavour' of delete I'd expect still does not appear to be being called (the breakpoint is never hit).
That is, I still only see the vector deleting destructor.
Therefore, that may have been a 'red herring'.

Perhaps this is just a Microsoft implementation issue,
or perhaps there's still some other subtlety I've missed.

站稳脚跟 2024-09-19 00:55:20

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

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

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

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

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

要检查是否存在此类问题,只需禁止使用向量删除运算符即可。

This behavior is special to MSVC 9, where the delete operator for a exported class, that has a virtual destructor is implicitly generated and mangled to vector dtor with an associated flag, where 1 means (scalar) and 3 means (vector).

The true problem with this thing is, that it breaks the canonical form of new/delete, where the client coder is not able to disable the vector delete operator in its code, if he thinks, that it is a bad idea to use it.

Moreover the vector dtor, also seems to be executed wrong, if the new is allocated in another module than the module the implementation resides in and is then hold in a static variable via a reference count, which executes a delete this (here the vector dtor comes into play) on process shutdown.

This matches to the heap problem 'bshields' already mentioned before, the dtor is executed on the wrong heap and the code crahses either with 'cannot read that memory location' or 'access violation' on shutdown - such problems seem to be very common.

The only way around this bug, is to forbid the usage of a virtual destructor and execute it your own, by enforcing the usage of a delete_this function from the base class - stupid as it is you imitate the stuff the virtual dtor should do for you. Then also the scalar dtor is executed and on shutdown any ref counted objects shared between modules, can be instantiated in a safe way, because heap is always addressed correctly to the origin module.

To check, if you have such problems, simply forbid the usage of the vector delete operator.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文