I understand that in .NET, finalizers are run even if an object is partially constructed (e.g. if an exception is thrown out of its constructor), but what about when the constructor was never run at all?
Background
I have some C++/CLI code that does effectively the following (I don't believe this is C++/CLI specific, but this is the situation I have at the ready):
try {
ClassA ^objA = FunctionThatReturnsAClassA();
ClassB ^objB = gcnew ClassB(objA); // ClassB is written in C# in a referenced project
...
}
catch (...) {...}
I have a 100% repeatable case where, if an exception is thrown out of FunctionThatReturnsAClassA(), and then a GC is triggered (seems to be reliably triggered by running this code again, but waiting a while also works), ClassB's finalizer is called.
Now, via trace output I can confirm that ClassB's constructor is not running (which is of course what you'd expect). So somehow, objB was apparently allocated and added to the finalizer list, before the preconditions for calling its constructor were even met (i.e. collecting the result from FunctionThatReturnsAClassA()).
This only happens in optimized release builds running outside the debugger. There are a variety of small changes I can make that result in the finalizer not running -- for instance inserting another method call between the two statements, or (tellingly, I think) moving the "gcnew ClassB" into a separate function that returns the object.
It seems to me that somehow the allocation part of the gcnew statement is getting reordered and run before the previous statement, but this reordering is NOT reflected in the generated MSIL code (defeating my initial assumption that this was just another C++/CLI code gen bug). Further, comparing the generated MSIL code between the "buggy" state and any of the "fixed" states shows no unexpected structural changes.
I've looked at the generated x86 code in the debugger as well and it doesn't look strange so far, but I haven't analyzed it as deeply and anyway I can't reproduce this behavior in the debugger so I'm not 100% sure the code I get from the debugger is the same as the code that shows the strange behavior.
So it could be an MSIL->x86 code gen quirk or it could be a processor instruction reordering (the former seems more likely but I haven't confirmed by trying harder to get the exact code in memory when the behavior occurs -- this is my next step).
Question
So is it valid (for lack of a better term) for the allocation of an object in .NET to be divorced and reordered separately from the constructor call for that object?
A constructor method is used to initialize an object, while finalizer methods are called just before the object is garbage-collected and its memory reclaimed. The syntax of the finalizer method is simply finalize(). The Object class defines a default finalizer method.
Finalizers (historically referred to as destructors) are used to perform any necessary final clean-up when a class instance is being collected by the garbage collector. In most cases, you can avoid writing a finalizer by using the System. Runtime. InteropServices.
A destructor is run when the program explicitly frees an object. A finalizer, by contrast, is executed when the internal garbage collection system frees the object.
Finalizers are time-limited and the GC will terminate a finalizer if it blocks for too long - this is the primary reason to avoid it where possible.
As covered in comments, the answer is "Yes" -- a finalizer can run if a constructor didn't run or didn't complete. However a finalizer cannot run if an allocation didn't occur (which is independent of the constructor call).
This is now confirmed to be a JIT optimization bug: https://github.com/dotnet/coreclr/issues/2478
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With