Why is vector deleting destructor being called as a result of a scalar delete?

北战南征 提交于 2019-11-30 14:57:19

问题


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?


回答1:


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.




回答2:


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




回答3:


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.




回答4:


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.




回答5:


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



来源:https://stackoverflow.com/questions/3373193/why-is-vector-deleting-destructor-being-called-as-a-result-of-a-scalar-delete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!