locked
Finalizer intermittently not called when debugging RRS feed

  • Question

  • Hello!

    I'm working on a MFC application which has a managed part implemented in C++/CLI using .NET 4. The environment is Visual Studio 2013.

    The application has a reference class which has member that is a pointer to a native C++ class. The constructor allocates an instance of this native class and the finalizer deallocates it by calling delete on it. It looks something like this:


    ManagedClass::ManagedClass()
    {
      nativePointer = new NativeClass();	
    }
    
    ManagedClass::~ManagedClass()
    {
      this->!ManagedClass();
    }
    
    ManagedClass::!ManagedClass()
    {
      delete nativePointer;
    }

    The application has one instance of the ManagedClass itself which is static. No attempts are made to "clean up" explicitly by using delete on the instance. I may be wrong here, but I expect the garbage collector to call the finalizer on this object as a part of application shutdown (this assumption may be wrong of course). When using Visual Studio and debugging I notice that this sometimes happens and sometimes it does not. When it does not, Visual Studio reports the instance of the "NativeClass" above as a detected memory leak.

    My questions are:

    1) Is my assumption above wrong when thinking the finalizer should be called during shutdown?

    2) What is the "right way" to deal with this situation so there is no memory leak and so that Visual Studio doesn't consider this one?

    Thanks in advance!

    /Johan

    Monday, June 16, 2014 7:41 AM

Answers

  • 1) Finalizers should run if the process exits cleanly - by returning from main/WinMain or by using exit(). If your application ever calls ExitProcess then finalizers won't run.

    There may be a scenario where the memory leak is always reported but it's unlikely to be your case - it involved a dynamically loaded MFC dll, that's not the case with a MFC application.

    2) Delete the managed object before exiting the application. Perhaps via a global native object:

    #include <stdio.h>
    #include <crtdbg.h>
    
    struct native {
        native() { printf("native ctor\n"); }
        ~native() { printf("native dtor\n"); }
    };
    
    ref struct managed {
        static managed^ f = gcnew managed;
        native *p;
    
        managed() : p(new(_CLIENT_BLOCK, __FILE__, __LINE__) native) {
        }
    
        ~managed() {
            this->!managed();
        }
    
        !managed() {
            delete p;
            p = nullptr;
        }
    };
    
    void start() {
        // do something with managed::f so static field gets initialized
        System::Console::WriteLine(managed::f);
    }
    
    struct cleanup {
        ~cleanup() {
            printf("start cleanup\n");
            delete managed::f;
            printf("done cleanup\n");
        }
    } cleanup;
    

    Monday, June 16, 2014 12:23 PM

All replies

  • 1) Finalizers should run if the process exits cleanly - by returning from main/WinMain or by using exit(). If your application ever calls ExitProcess then finalizers won't run.

    There may be a scenario where the memory leak is always reported but it's unlikely to be your case - it involved a dynamically loaded MFC dll, that's not the case with a MFC application.

    2) Delete the managed object before exiting the application. Perhaps via a global native object:

    #include <stdio.h>
    #include <crtdbg.h>
    
    struct native {
        native() { printf("native ctor\n"); }
        ~native() { printf("native dtor\n"); }
    };
    
    ref struct managed {
        static managed^ f = gcnew managed;
        native *p;
    
        managed() : p(new(_CLIENT_BLOCK, __FILE__, __LINE__) native) {
        }
    
        ~managed() {
            this->!managed();
        }
    
        !managed() {
            delete p;
            p = nullptr;
        }
    };
    
    void start() {
        // do something with managed::f so static field gets initialized
        System::Console::WriteLine(managed::f);
    }
    
    struct cleanup {
        ~cleanup() {
            printf("start cleanup\n");
            delete managed::f;
            printf("done cleanup\n");
        }
    } cleanup;
    

    Monday, June 16, 2014 12:23 PM
  • Thanks for your answer!

    So basically this is something in the development environment/.NET "misbehaving" (and yes, the application exits in a "normal" fashion)?

    Monday, June 16, 2014 7:23 PM
  • I wouldn't expect a finalizer to get called on application shutdown.  The finalizer is called when the GC goes to reclaim the memory.  However on application shutdown, I would expect the GC to abandon all memory wholesale -- the OS will reclaim the heap anyway at that point so no reason to spend CPU cycles cleaning up a bunch of stuff that is about to go away.  A finalizer and a native C++ destructor are very different beasts.
    Monday, June 16, 2014 7:42 PM
  • "So basically this is something in the development environment/.NET "misbehaving""

    It's more likely that what happens is that things are slower in the debugger and the CLR gets impatient and kills the finalizer thread before all objects are finalized.

    "I wouldn't expect a finalizer to get called on application shutdown."

    Yet this is exactly what happens in most circumstances.

    "However on application shutdown, I would expect the GC to abandon all memory wholesale"

    Yes but finalization isn't strictly about GC, a GC isn't required to finalize objects. When a finalizable object is created it is placed into the finalization queue. During normal CLR shutdown the finalization queue is processed.

    Monday, June 16, 2014 9:10 PM