Click here to Skip to main content
15,891,943 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Say we have this code:

Thing^ MyManagedObject = gcnew Thing();
MyManagedObject->DoStuff();
MyManagedObject = gcnew Thing();
//MyManagedObject is now random memory


On the second gcnew it creates the new Thing and puts the reference in MyManagedObject, it the goes on to call the destructor for Thing. The only problem is that it doesn't call the destructor for the Thing that has gone out of scope, but my new Thing that I'm holding the reference to.

I though the problem was limited to managed objects but even with some unmanaged objects when running in release it messes things up, take this example:

Matrix Transform = OtherMatrix; //OtherMatrix successfully copied to Transform
//Do Stuff
Transform = OtherMatrix * SomethingElse;


The multiplication of the matrices created a new matrix, which is then destructed before being copied into Transform.

Putting some debug output in the copy ctor / assignment operator (same thing happens with both) and in the destrutor you get this in the output
Destroying matrix: 0x0028f140
Copying matrix: 0x0028f140 to 0x0028f080

Rather than the other way around.

Has anybody come across this before? I'm using VS2012 for this one, but maybe I'll open it up in 2010 and see if it manages to screw things up just as good.

I can't be the only one with this issue, maybe I did something stupid, maybe somebody else did something stupid but there'd better be a fix.

<------------------------------------>
EDIT: Case 1 was caused by my own stupidity (long story short, custom copy ctor etc. required)

Case 2 seems to be an issue with some optimisation. I've not figured out how to get around it or why it's decided to start giving me issues now. My matrix class is templated, there is a specialisation for floats that uses sse instructions for a lot of the functions. The multiplication operator looks like this:

(Matrix& A, Matrix& B)
{
 Matrix<float> Result;
 //Load addresses of A, B and Result to registers
 //Do multiplication using sse 
 //store final matrix in Result
 return Result;
}


Result ends up with the correct values, but the caller of the multiplication never sees it. Matrix Result = MatA * MatB; //Result ends up with all 0's

It seems to be return value optimisation, if I modify values for the result outside of the asm, I see the changes, anything done in the asm however is not seen. Strange that the compiler has been clever enough (or not tried to be too clever) in the past to avoid this issue.
Posted
Updated 4-Feb-13 13:46pm
v4

You should never rely on the order or time of calling destructors in .NET. The idea and practice of using destructors in a managed system is radically different from "traditional" object lifetime control systems used with C++ and similar languages. By this reason, constructors in C++/CLI are written very, very rarely, compared to C++. In most cases, they are just not needed, because most of the destructor chores are done by the Garbage Collector (GC), and… by exact same reason: you cannot rely on the time or order of destructor calls.

GC operation is based on the notion of reachability of an object. The actual call of the object destructors and the act of reclaiming memory cannot be predicted bu the application (by the way, this is the reason why destructores are rarely written in the typical .NET applications, usually this is not needed and can created some problems of race conditions type), but it happens when some object becomes unreachable.

By the way, this is not so trivial criterion as you might think. For example, if object A references object B, object B references object C, and C reference A, this cyclic dependency does not prevent GC from figuring out that all objects should be garbage-collected if there are no other referenced to them. This is easy to check up by a simple experiment.

You can find the detail of reachability and garbage collection here: http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29[^].

Please see also my past answer: When CLR Do Automatic Memory Management and GC?[^].

Good luck,
—SA
 
Share this answer
 
Comments
Anthony Mushrow 4-Feb-13 16:53pm    
It's not the actual time that an object is destructed that I'm worried about, It's that objects are destructed before I can use them. Surely something is flawed here, how can I possibly make use of the result of a matrix multiplication if the object is destroyed before I get to see it?

And why would my second thing object be destroyed, it not a few seconds later or when the object is finally destroyed it's immediate. After making the second Thing object if I try to use it the application will crash because I'm trying to reference on object that has been destroyed. These issues aren't present in a plain C# project.
Sergey Alexandrovich Kryukov 4-Feb-13 17:02pm    
I cannot believe this could be the case in principle. Rather your observation could be wrong, maybe due to your method of experimenting.

However, who knows? If you really want to sort it out to the very end, you would need to provide some sample project, this time, totally complete; and it should reproduce this problem; and you need to describe the steps to reproduce. If you can do this, and I can test it myself, only then I can believe it; and most likely, explain.

—SA
Espen Harlinn 4-Feb-13 19:47pm    
Good points :-D
Sergey Alexandrovich Kryukov 4-Feb-13 20:06pm    
Thank you, Espen.
—SA
Let's put it this way: If what you claim would really be true, no C++/CLI could possibly run successfully. That's the reason you are so surprised. And how so often, the solution might be found in the code with which you are measuring this behaviour o rin the way you are interpreting the result.

So my recommendation is: Show us the destructor and copy-constructor code that prints those outputs from which you are deriving this interpretation.

In addition, the two cases you are showing are very different regarding the memory allocation scheme. The first example is about a reference class, the second about a value class. So what should happen in theory in your first example:
C++
Thing^ MyManagedObject = gcnew Thing();
MyManagedObject->DoStuff();
MyManagedObject = gcnew Thing();

The second gcnew call assigns a new reference to MyManagedObject. Thereby, the reference to the first Thing object is lost. Nothing more. The GC will clean it up some time later; probably not right away but many hundred or thousand gcnew calls later. So I wouldn't be surprised if the destructor for the first Thing object wasn't called for a long time, perhaps even as late as the end of your program.

Now, perhaps with this new expectation in mind, you check your code of the Thing class and see if it would allow that interpretation.

Your second example is a totally different case:
C++
Matrix Transform = OtherMatrix; //OtherMatrix successfully copied to Transform
Transform = OtherMatrix * SomethingElse;


Matrix is obviously a value class and its memory allocated on the stack, not the heap. The destructor for the Transform object should actually not be called here, instead I would expect the assignment operator to be executed. The compiler generates code for multiplying OtherMatrix with SomethingElse and the result is a temporary object of type Matrix that is allocated on the stack. This temporary object must be destroyed after assigning it to Transform. That is perhaps the destructor call you are observing. So the sequence I would expect is:
constructor call for creating the temporary object
operator*
assignment operator to transfer the result to Transform
destructor call to destroy the temporary object

Please review the printing code in the destructor and copy constructors and see if you are interpreting the results in the right way. Feel free to amend your question with your printing and test code that led you to this assumption and we will take a look at it. I am quite positive that after the dust settles everything will fall into place again.
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 4-Feb-13 20:07pm    
Good points, my 5.
As I say, what OP observes, could be a result of inaccurate way of experimenting.
—SA
nv3 5-Feb-13 2:15am    
Thanks, SA. It was just an amendment to the general principals which you already had layout out nicely in your answer.
>> The multiplication of the matrices created a new matrix,
>> which is then destructed before being copied into Transform.

My best guess is that you're having some trouble inside the assignment operator.

I can only assume that you have something like this:
class Matrix
{
  struct MatrixData
  {
    long referenceCount;
    int rows, int cols;
    double[0] data;
  };
  Matrix();
  Matrix(int rows, int cols, double* data);
  Matrix(const Matrix& otherMatrix);
  
  // I guess something strange is going on inside 
  // the assignment operator, perhaps a delete this??
  Matrix& operator = (const Matrix& otherMatrix);  

};


Best regards
Espen Harlinn
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 4-Feb-13 20:08pm    
I may make sense, my 5. It would be not hard to research having 100% complete code sample...
—SA
Espen Harlinn 4-Feb-13 20:27pm    
Here is omething I hope you'll like: http://www.codeproject.com/Articles/539202/CIMTool-for-Windows-Management-Instrumentation-Par
Sergey Alexandrovich Kryukov 4-Feb-13 21:23pm    
Certainly interesting... Does it really use DevExpress, as MalamberOrg commented? I would agree it's would be a big drawback. UI design looks nice though...
—SA
Anthony Mushrow 5-Feb-13 5:40am    
I think I've figured this one, the multiplication uses inline asm to do the actual work in release we get return value optimization, the result of which means that the asm no longer stores the result in the correct place, or rather the compiler moves the goalposts and now I have no idea where the value should be stored.
Espen Harlinn 5-Feb-13 6:15am    
>> inline asm to do the actual work
That's good
>> in release we get return value optimization
Sounds strange - how things are returned should be determined by the calling convention. I've noticed that the VS C++ compiler sometimes does strange things to your code when you try to tell it to do it's damned best in terms of optimisation ...

I usually turn my Matrix classes into some sort of smart pointers, which ensures that passing them around is pretty efficient and predictable.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900