📜 ⬆️ ⬇️

The global override problem of new / delete in C ++ / CLI

As you know, C ++ allows you to globally redefine the new and delete operators. Typically, this override is used to diagnose, locate memory leaks and more efficiently allocate memory.

We use all this in our large project. However, we have a part written in C #, which with C ++ / CLI interacts with the main part in C ++. And here there were problems. We got memory leaks where they could not be.

It all came down to the fact that our new was called, but delete was crt'shny.
To deal with the problem, I created a test solution where I modeled the situation. For simplicity, the redefined functions look like this:
void * __cdecl operator new ( size_t size )
{
printf( "New\n" );
return malloc(size);
}

void __cdecl operator delete( void *p )
{
printf( "Delete\n" );
free(p);
}


So, we have to get the same amount of New and Delete in the output, equal to the number of pairs new / delete.

Here is the code that calls new / delete:
//managed.h
namespace Managed
{
public ref class ManagedClass
{
public :
ManagedClass()
{
NativeClass* cl = new NativeClass();
delete cl;
int * a = new int ();
delete a;
}
};
}

//native.cpp
NativeClass::NativeClass()
{
int * a = new int ();
delete a;
}


From C # we create a ManagedClass like this:
static void Main( string [] args )
{
ManagedClass cl = new ManagedClass();
}


There is also a class Foo, which lies in a separate file basic.h, and it is not used in any way in these classes. Just its definition is placed in the Precompiled Header.
This “bad” class does not carry functionality, but the mere presence of it leads to a very strange result. If you comment on the class copy constructor, then everything works fine:
#pragma once

class Foo
{
public :
Foo() {}
//Foo( const Foo& ) {}
Foo& operator = ( const Foo& ) { return * this ; }

virtual ~Foo() {}
};


Conclusion:
New
New
Delete
Delete
New
Delete
')
3 new - 3 delete. All is well.

If the copy constructor is not commented out, then not all of our news correspond to our delete:
#pragma once

class Foo
{
public :
Foo() {}
Foo( const Foo& ) {}
Foo& operator = ( const Foo& ) { return * this ; }

virtual ~Foo() {}
};


Conclusion:
New
New
Delete
New

3 new - 1 delete. Everything is bad.

Bottom line: we have an error in choosing the delete function, which depends on the “position of the moon”. It's time to start google.

During the search, a document was found that supposedly describes our problem:
http://support.microsoft.com/kb/122675.

Unfortunately, after a detailed reading of the document, it turns out that our case is different from the one we have examined. Moreover, this behavior is not observed in the pure-C ++ project. The problem is in C ++ / CLI.

We continue our research. If the destructor is not made virtual, then again everything works as it should:
#pragma once

class Foo
{
public :
Foo() {}
Foo( const Foo& ) {}
Foo& operator = ( const Foo& ) { return * this ; }

~Foo() {}
};


Conclusion:
New
New
Delete
Delete
New
Delete

It is worth noting that this class is not used in any way in our program. Just the very presence of it leads to a bug.
Because the bug only appears in C ++ / CLI, then we will try to mark this class as unmanaged:
#pragma managed(push,off)
#include "../System/basic.h" // h “” Foo
#pragma managed(pop)


Conclusion:
New
New
Delete
Delete
New
Delete

Happened! However, this is not a solution. We will dig further.

Maybe the problem is in the Precompiled Headers? However, if basic.h is from Stdafx.h, but paste it into any other * .h file that falls into the project, the problem will repeat.

Let's see in more detail what the linker does. To do this, turn on the mode of displaying additional information from the linker:
image

Wow! He found delete in MSVCRTD.lib, which is why not ours is used:
image

In the case when we make the destructor non-virtual or comment on the copy constructor, we get the following linker output:
image

Moreover, if you leave the copy constructor, make the destructor not virtual, but add a virtual function, again delete starts to fail.

Although the studies were in the Debug build, the same behavior was also observed in Release.

Unfortunately, this problem has not yet been resolved, which makes it impossible to simply search for memory leaks in a project.

As a result, if there is a class that has a virtual table and a copy constructor (and this class is compiled into managed), the linker links the standard delete, although we have the overridden delete in dll.

Ps. Who is interested in working independently with the simplest test solution with a problem and, perhaps, with advice, can download it here: https://mysvn.ru/Lof/NewDeleteProblem

Source: https://habr.com/ru/post/99142/


All Articles