Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inline class constructor to avoid vc memory crash

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;
}
like image 663
feelfree Avatar asked May 24 '13 13:05

feelfree


1 Answers

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.

like image 141
Hans Passant Avatar answered Sep 18 '22 17:09

Hans Passant