Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debug Assertion Failed! Expression: _pFirstBlock == pHead

I am calling into a statically linked .dll, and I see this error:

enter image description here

I wrote both the .dll and the calling code. This error should not be occurring. I am wondering if anyone else has encountered it before? The .dll only contains about 10 lines of code, its just a test .dll to see how dlls work in general. It blows up when I pass a std::string back out of the .dll.

I am using Visual Studio 2012 and C++.

What I will try next

From Debug assertion... _pFirstBlock == pHead:

This problem can occur if one uses the single-threading libraries in a multithreaded module.

Tomorrow, I'll try recompiling the Boost static libraries in multi-threaded mode (my .dll is set to multi-threaded static mode).

What I will try next

See Using strings in an object exported from a DLL causes runtime error:

You need to do one of two things

  1. Make both the DLL and the client that use it both link to the DLL version of the CRT (e.g. not statically).
  2. OR You need to make sure you don't pass dynamically allocated memory (such as is contained in string objects) across DLL boundaries. In other words, don't have DLL-exported functions that return string objects.

Joe

This seems to match whats going on, it blows up at the precise point where I pass a string back across a .dll boundary. The problem only occurs when everything is linked in static mode. Now that's fixable.

See Passing reference to STL vector over dll boundary.

What I will try next

See Unable to pass std::wstring across DLL.

Solution

I have a nice solution, see the answer below.

like image 218
Contango Avatar asked Sep 18 '13 21:09

Contango


2 Answers

In this case, the problem is that I was passing a std::string back across a .dll boundary.

Runtime Library config

  • If the MSVC Runtime library is set to Multi-threaded Debug DLL (/MDd), then this is no problem (it works fine).

  • If the MSVC Runtime library is set to Multi-threaded Debug (/MTd), then it will throw this error, which can be fixed with the following instructions.

Memory allocated in Memory Manager A and freed in Memory Manager B ...

The problem is that memory is allocated on the .dll side, then that same memory is freed on the application side. This means that memory manager A is allocating memory, and memory manager B is releasing that same memory, which generates errors.

The solution is to make sure that all memory passed back is not allocated in the DLL. In other words, the memory is always allocated on the application side, and freed on the application side.

Of course, the DLL can allocate/free memory internally - but it can't allocate memory that is later freed by the application.

Examples

This will not work:

// Memory is allocated on the .dll side, and freed on the app side, which throws error. DLL std::string GetString();  

This will work:

// Memory is allocated/freed on the application side, and never allocated in the .dll. DLL int GetString(std::string& text);  

However, this is not quite enough.

On the application side, the string has to be pre-allocated:

std::string text(""); text.reserve(1024);     // Reserves 1024 bytes in the string "text". 

On the .dll side, the text must be copied into the original buffer (rather than overwritten with memory that is allocated on the .dll side):

text.assign("hello"); 

Sometimes, C++ will insist on allocating memory anyway. Double check that the pre-allocation is still the same as it was:

if (text.capacity < 1024) {    cout << "Memory was allocated on the .dll side. This will eventually throw an error."; } 

Another way that works is to use std::shared_ptr<std::string>, so even though memory is allocated in the .dll, it is released by the .dll (rather than the application side).

Yet another way is to accept a char * and a length which indicates the amount of pre-allocated memory. If the text that we want to pass back is longer than the length of pre-allocated memory, return an error.

like image 83
Contango Avatar answered Sep 22 '22 03:09

Contango


This is what assert() looks like when its expression argument evaluates to false. This assert exists in the Debug build of the C runtime library, designed to check for allocation problems. The free() function in your case. The Debug build add extra checks to make sure you are writing your code correctly. And tell you when it detects a problem. Like calling free() on an allocation that was already freed, the simple case. Or calling free() passing the wrong pointer value, the trickier case. Or calling free() when the heap was corrupted by earlier code, the much harder case.

This is only as far as they can take it, they don't actually know why your code got it wrong. There is not any way they can put a Big Red arrow on the code that corrupted the heap for example. The easy case is covered by the Debug + Windows + Call Stack debugger window, it takes you to the code in your program that called free(). Or std::operator delete for a C++ program. The harder case is very, very hard indeed, heap corruption is often a Heisenbug. Getting the assert to be repeatable so you can set a data breakpoint on the reported address is the core strategy. Crossing fingers for the easy case, good luck with it!


After edit: yes, having cross-module problems with a C++ class like std::string is certainly one of the problems it can catch. Not a Heisenbug, good kind of problem to have. Two basic issues with that:

  • The modules might each have their own copy of the CRT, objects allocated by one copy of the CRT cannot be released by another copy of the CRT. They each have their own heap they allocate from. A problem that got addressed in VS2012, the CRT now allocates from a process-global heap.
  • The modules might not use the same implementation of std::string. With an object layout that does not match. Easily induced by having the modules compiled with different C++ library versions, particularly an issue with C++11 changes. Or different build settings, the _HAS_ITERATOR_DEBUGGING macro is quite notorious.

The only cure for that problem is to make sure that you build all of the modules in your program with the exact same compiler version using the exact same build settings. Using /MD is mandatory, it ensures that the CRT is shared so there's only one in the program.

like image 35
Hans Passant Avatar answered Sep 24 '22 03:09

Hans Passant