7

C++ class constructor can be inlined or not be inlined. However, I found a strange situation where only inline class constructor can avoid Visual Studio memory crash. The example is as follows:

dll.h

class _declspec(dllexport) Image 
{
    public:
        Image();
        virtual ~Image();
};

class _declspec(dllexport) Testimage:public Image 
{
public:
    Testimage();

    virtual ~Testimage();
};

typedef std::auto_ptr<Testimage> TestimagePtr;

dll.cpp

#include "dll.h"
#include <assert.h>


Image::~Image()
{
            std::cout<<"Image is being deleted."<<std::endl;
}
Image::Image()
{
}

Testimage::Testimage()
{

}

Testimage::~Testimage()
{
        std::cout<<"Geoimage is being deleted."<<std::endl;
}

The dll library is compiled as a dynamic library, and it is statically linked to the C++ runtime library (Multi-threaded Debug (/MTd)). The executable program that runs the library is as follows:

int main()
{
    TestimagePtr my_img(new Testimage());
    return 0;
}

The executable program will invoke the dll library and it also statically links the runtime library. The problem I have is that when running the executable program the following error message appears: enter image description here

However, when the class constructor in dll is inlined as the following codes show:

class _declspec(dllexport) Image 
{
    public:
        Image();
        virtual ~Image();
};

class _declspec(dllexport) Testimage:public Image 
{
public:
    Testimage()
    {
    }

    virtual ~Testimage();
};

The crash will disappear. Could someone explain the reason behind? Thanks! By the way, I am using VC2010.

EDIT: The following situation also trigger the same crash .

Situation 1

int main()
{
    //TestimagePtr my_img(new Testimage());
    Testimage *p_img;
    p_img = new Testimage();
    delete p_img;
    return 0;
}
feelfree
  • 11,175
  • 20
  • 96
  • 167
  • Try dynamically linking against the CRT with /Md – Samuel May 24 '13 at 13:40
  • @Samuel Thanks! For some reason I have to link the runtime library statically. – feelfree May 24 '13 at 13:42
  • 1
    Can you temporarily change it and check if this works? Also: Can you tell me why you have to link statically against it? I am quite curious – Samuel May 24 '13 at 16:18
  • @Samuel Probably to distribute only one binary ? – Liviu May 24 '13 at 16:27
  • @feelfree I'm so curious with the answer to this question :p ! – Liviu May 24 '13 at 16:28
  • Well the redistributable of vc is not that large that you want to link statically and introduce lots of fine errors like heap corruption and separate heaps, etc. Try this: Remove the auto ptr and call new and delete in main, see if this crashes – Samuel May 24 '13 at 16:37
  • @Samuel I have changed linking against the CRT with /MDd, and it works. The reason why I need static link is it is more stable as discussed in http://stackoverflow.com/questions/1344126/memory-allocation-and-deallocation-across-dll-boundaries. – feelfree May 24 '13 at 16:38
  • @Samuel I have tried new and delete, and the same crash happens. – feelfree May 24 '13 at 16:39
  • 3
    I will try to reproduce the case. In the meanwhile why do you think /Mt is more stable? As you can see it is not. By linking statically against the CRT you explicitly create multiple heaps. One in the dll and another in the host. So allocating memory in the dll and deleting it in the host kills your app. What you call inlining is infact not inlining. When you implement the ctor in the header and include it, you bring the ctor into the host heap, that's why it does not crash in this case – Samuel May 24 '13 at 16:54
  • 2
    @Samuel It shouldn't matter whether the constructor is inline or not: it "only" matters that `new` and `delete` use the same heap. `new` is always in the host, I don't understand where `std::auto_ptr` is taken of. – Liviu May 24 '13 at 17:00
  • @Samuel I am sorry that I give you the wrong link that explains why /Mt is more stable. If different dll share different CRT versions, dynamic link can cause problem. Check here http://stackoverflow.com/questions/1634773/freeing-memory-allocated-in-a-different-dll?rq=1. Please pay attention to what erbi suggested. – feelfree May 24 '13 at 17:04
  • Well, I was not able to reproduce it with VC 2012, I may have some settings wrong (although I ensured the static linking of CRT). Now, but I am afraid but you got this wrong (or did I)? Erbi says that you should share the heaps as often as possible but sometimes you cant when modules are compiled with different major CRT versions. Basically even the answerer suggest using /MDd as often as possible – Samuel May 24 '13 at 17:14
  • @Samuel Thanks for the discussion. For me I am more curious with the question why it will crash if static link is enforced. – feelfree May 24 '13 at 17:26
  • Ok, what is the error message in the output window after you hit continue on the message above? What is the callstack when you break in? – Samuel May 24 '13 at 17:46
  • @Samuel - you cannot repro this with VS2012. It is the first version of VS that solves this problem, it has a CRT that allocates from the default process heap. – Hans Passant May 24 '13 at 19:08
  • 1
    Can you provide the source for that? It's quite interesting to know – Samuel May 24 '13 at 19:10
  • @Samuel Sorry to reply late, and the projects can be downloaded from https://dl.dropboxusercontent.com/u/92688392/sandbox.zip. Please build the dll first, and then the exe. – feelfree May 27 '13 at 09:38
  • Sorry, I was talking to Hans Passant, not to you. As I only have 2012 I won't be able to reproduce it. What I meant is the source of Hans Passant's point that VC+ 2012 uses the process heap. I've found it under: http://msdn.microsoft.com/en-US/library/bb531344.aspx CRT Library, first bullet – Samuel May 27 '13 at 11:08

2 Answers2

13

it is statically linked to the C++ runtime library (Multi-threaded Debug (/MTd)

This is a very problematic scenario in versions of Visual Studio prior to VS2012. The issue is that you have more than one version of the CRT loaded in your process. One used by your EXE, another used by the DLL. This can cause many subtle problems, and not so subtle problems like this crash.

The CRT has global state, stuff like errno and strtok() cannot work properly when that global state is updated by one copy of the CRT and read back by another copy. Relevant to your crash, a hidden global state variable is the heap that the CRT uses to allocate memory from. Functions like malloc() and ::operator new use that heap.

This goes wrong when objects are allocated by one copy of the CRT and released by another. The pointer that's passed to free() or ::operator delete belongs to the wrong heap. What happens next depends on your operating system. A silent memory leak in XP. In Vista and up, you program runs with the debug version of the memory manager enabled. Which triggers a breakpoint when you have a debugger attached to your process to tell you that there's a problem with the pointer. The dialog in your screenshot is the result. It isn't otherwise very clear to me how inlining the constructor could make a difference, the fundamental issue however is that your code invokes undefined behavior. Which has a knack for producing random outcomes.

There are two approaches available to solve this problem. The first one is the simple one, just build both your EXE and your DLL project with the /MD compile option instead. This selects the DLL version of the CRT. It is now shared by both modules and you'll only have a single copy of the CRT in your process. So there is no longer a problem with having one module allocating and another module releasing memory, the same heap is used.

This will work fine to solve your problem but can still become an issue later. A DLL tends to live a life of its own and may some day be used by another EXE that was built with a different version of the CRT. The CRT will now again not be shared since they'll use different versions of the DLL, invoking the exact same failure mode you are seeing today.

The only way to guarantee that this cannot happen is to design your DLL interface carefully. And ensure that there will never be a case where the DLL allocates memory that the client code needs to release. That requires giving up on a lot of C++ goodies. You for example can never write a function that returns a C++ object, like std::string. And you can never allow an exception to cross the module boundary. You are basically down to a C-style interface. Note how COM addresses this problem by using interface-based programming techniques and a class factory plus reference counting to solve the memory management problem.

VS2012 has a counter-measure against this problem, it has a CRT version that allocates from the default process heap. Which solves this particular problem, not otherwise a workaround for the global state issue for other runtime functions. And adds some new problems, a DLL compiled with /MT that gets unloaded that doesn't release all of its allocations now causes an unpluggable leak for example.

This is an ugly problem in C++, the language fundamentally misses an ABI specification that addresses problems like this. The notion of modules is entirely missing from the language specification. Being worked on today but not yet completed. Not simple to do, it is solved in other languages like Java and the .NET languages by specifying a virtual machine, providing a runtime environment where memory management is centralized. Not the kind of runtime environment that excites C++ programmers.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Yeah, my experience with dll-using project is they better all be configured using .props to have same settings and use CRT from the shared dll. And if use MFC make them be 'mfc extension' DLLs. To ensure an overall compatible memory system. And I just can't imagine where the idea of static CRT being safer could come from. – Balog Pal May 31 '13 at 02:16
  • Note that you can't pass C++ objects across the ABI boundary anyway – the code on each side might expect different layouts. One option is to just deliver your library as source code that the user can recompile at will. You can do that even if the code is under a proprietary license. – Demi Oct 19 '16 at 18:00
3

I tried to reproduce your problem in VC2010 and it doesn't crash. It works with a constructor inline or not. Your problem is probably not in what you write here.

Your project is too hard to open as it seams to have its file pathes set in absolute, probably because generated with CMake. (So the files are not found by the compiler).

The problem I see in your code is that you declare the exported classes with _declspec(dllexport) directly written.

You should have a #Define to do this, and the value should be _declspec(dllimport) when read from the exe compilation. Maybe the problem comes from that.

iksess
  • 574
  • 4
  • 9
  • It's indeed better to use _declspec(dllimport) in clients, but using _declspec(dllexport) has the same behavior except some performance difference. That may not contribute to the crash. – Balog Pal May 31 '13 at 02:10
  • Are you sure of this ? as the class could also be defined in the Exe and exported (it would be only with a 'dllexport'). If dllimport and dllexport have the same behavior, how can the compiler select if the symbol comes from the Exe or from an external library ? (however I think this is not the cause of the problem, but the fact it is two Cmake projects and compilation options are hard to be seen) – iksess May 31 '13 at 07:49
  • dllexport binds directly to _Foo symbol. dllimport binds to imp_Foo, that is just a JMP _Foo in code, but all of those thunks are placed in a single segment. When DLL is loaded that way all the fixups will appear on a single memory page, leaving the rest untouched and shared. The linker is smart, if the symbol is defined, it will go the EXPORTS otherwise in IMPORTS. You can use depends or dumpbin to check it. – Balog Pal Jun 01 '13 at 14:04