c++dllvisual-studio-debuggingmsvcrt

MSVC CRT Debug Heap assert passing C++ STL object across binary boundaries


We have a host application and a DLL both built with the same compiler settings etc both using the static CRT /MT. (tested with VS2019 and VS2022)

Eventually we pass some simple structs that contain a couple STL objects, for example

struct MyData
{
    std::vector<std::string> entries;
};

...
MyData DLL::getMyData() const
{
    // ...
    return MyData();
}

Getting CRT heap assertion when we deallocate MyData on the host application side that has been retrieved via the DLL's API.

Debug Assertion Failed!

File: minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp
Line: 996

Expression: __acrt_first_block == header
host.exe!free_dbg_nolock(void * const block, const int block_use) Line 996  C++
host.exe!_free_dbg(void * block, int block_use) Line 1030   C++
host.exe!operator delete(void * block) Line 38  C++
host.exe!operator delete(void * block, unsigned __int64 __formal) Line 32   C++
host.exe!std::_Deallocate<16,0>(void * _Ptr, unsigned __int64 _Bytes) Line 255  C++
host.exe!std::_Default_allocator_traits<std::allocator<std::_Container_proxy>>::deallocate(std::allocator<std::_Container_proxy> & _Al, std::_Container_proxy * const _Ptr, const unsigned __int64 _Count) Line 670 C++ 
host.exe!std::_Deallocate_plain<std::allocator<std::_Container_proxy>>(std::allocator<std::_Container_proxy> & _Al, std::_Container_proxy * const _Ptr) Line 976    C++
host.exe!std::_Delete_plain_internal<std::allocator<std::_Container_proxy>>(std::allocator<std::_Container_proxy> & _Al, std::_Container_proxy * const _Ptr) Line 989   C++
host.exe!std::string::~basic_string<char,std::char_traits<char>,std::allocator<char>>() Line 3007   C++
host.exe!DLL::MyData::~MyData() C++

Oddly enough no errors are reported with Address Sanitizer in MSVC (and also with ASAN using clang/Xcode), and the application seems stable in Release builds, but in MSVC Debug builds always breaks in the dtor.

Notably this is normally at least 1 call site away from the DLL interface, so is there some scenario in which RVO is moving the object away from the DLL without copying it at the binary boundary? Kind of stumped on this one.


Solution

  • The simplest way to fix this is usually just to DLL export the MyData class (from memory defining the dtor as virtual can also fix this problem). Without that, because the dtor is inline, you are getting two copies of the dtor, one in the original DLL, one in exe. DLL::getMyData() is creating a new instance using the heap in the DLL. The Exe is then trying to free the data members using its own heap. MSVC then spews an error because that heap didn't see the original allocation.

    In the case of std::string (or any other container type in the stdlib), I'd strongly recommend making those members private at the very least. You could easily run into the same problem if a programmer directly accesses the member functions (e.g. reserve).

    Normally MSVC would give you a C4251 warning for this problem. Sounds like this warning may have been disabled in your build?