The problem of global redefinition 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, search for memory leaks, and more efficiently allocate memory.

    We use all this in our large project. However, we do have a part written in C # that, using C ++ / CLI, interacts with the main part in C ++. And here there were problems. We got memory leaks where they couldn’t 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 simulated a situation. For simplicity, overridden 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 should get the same amount of New and Delete in the output, equal to the number of new / delete pairs.

    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 still a class Foo, which lies in a separate basic.h file, 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 its mere presence 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 new 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 removal function, which depends on the “position of the moon”. It's time to start google.

    During the search, a document was found that allegedly 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 considered. Moreover, in a pure-C ++ project this behavior is not observed. The problem is in C ++ / CLI.

    We continue our research. If the destructor is made non-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 its very presence leads to a bug.
    Because The bug appears only in C ++ / CLI, then 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

    It turned out! However, this is not a solution to the problem. We will dig further.

    Could it be a problem in Precompiled Headers? However, if basic.h is from Stdafx.h, but paste it into any other * .h file that gets into the project, then the problem will recur.

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

    Wow! He found delete in MSVCRTD.lib, that’s why delete isn’t ours:
    image

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

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

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

    Unfortunately, this problem has not yet been solved, which makes it impossible to easily search for memory leaks in the project.

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

    PS. Those who are interested in independently working with the simplest test solution to the problem and, possibly, help with advice, can download it here: https://mysvn.ru/Lof/NewDeleteProblem

    Also popular now: