On 01/11/2016 at 11:40, xxxxxxxx wrote:
I guess, I have to add MAXON's point of view here.
While during development you might want a failed allocation to end up in a crash (which I (just my personal opinion) think, is pretty bad practice already), in your final plugin, this should not and must not happen. User will loose work and precious time. This is an absolute no go!
Here at MAXON such code is not considered bad practice, but plain wrong.
Next:
"Allocations rarely fail" is simply not true. They might not fail in your test environment, but in real world users tend to work with huge scenes with lots and lots of high poly objects and high res textures and even on today's machines it is very well possible to run out of memory. Believe me, you don't want to be the reason for a user to bite into his/her table.
Every allocation needs to be checked! Full stop.
In snippets in this forum (and also the ones I have below) these checks might be omitted to focus on some issues and to avoid too much code, but in real world code each and every allocation needs to be checked. And there's nothing funky going to happen afterwards, but your code will determine how to take care of such error condition.
So please never do something like this. Rather use a DebugStop() (or even a CriticalStop()) to be informed about an allocation problem during development. But this error branch has to work without crash, when you deem your development done.
So do something like this:
BaseMaterial *mymat = BaseMaterial::Alloc(Mmaterial);
if (myMat == nullptr)
{
DebugStop(); // TODO: handle error gracefully
return;
}
Next: Ownership:
I don't want to be picky, but I think, we should get the details right here.
First some terms:
A pointer variable points to a certain memory address (nothing more and nothing less).
nullptr (or NULL in older versions of the SDK is a value of an invalid memory address (not necessarily zero!).
Please take a look at the terms Heap and Stack (or Call Stack) yourself, they are somewhat important in this context.
For now very simplified:
The stack is not only containing the order of calls to your functions (and their parameters) but also local variables. The stack grows and shrinks dynamically during a programs execution.
In this context we care only about the local variables:
void myFunc()
{
Int32 myVar;
// ...
}
So when this function is called (besides other things) there's made some room on the stack for myVar. And when the function is left, this space will automatically be "freed" again.
So following line from Scott:
BaseMaterial *mymat = nullptr; // <--- you own this pointer. So you must use free on it yourself to kill it without memory leaks
Well, technically not quite, the pointer resides on the stack. It is just a local variable. The concept of ownership is not relevant at this point.
Heap is an area of memory (actually an address space to be precise) a process can utilize for memory allocation. Of course this memory is not endlessly large. So whenever memory gets allocated, the underlying memory management reserves a section of the heap and returns a pointer to this section. At this point the concept of ownership comes into play. The allocating code gets owner of this section of memory and is responsible for deallocating/freeing this memory when it is no longer needed. So it is absolutely mandatory to not loose this pointer, otherwise you have a memory leak and this section is no longer usable for any other part of the program.
Example of a memory leak, kids don't do this at home:
void memoryLeak()
{
// Memory is allocated and you are automatically owner of the allocated memory.
// The resulting pointer is stored in a local variable on the stack
BaseMaterial* mat = nulltpr; // just a pointer variable on the stack, no ownership involved, yet
mat = BaseMaterial::Alloc(Mmaterial); // now, memory got allocated and you own the memory that the pointer points to
// Now, leaving the function, the local pointer will be lost.
// Result, you have no more reference to that part of memory and are not able to deallocate it later on.
return;
}
Possible solutions:
- You want to stay owner of this memory, then store the pointer in a somewhat permanent place, for example a member variable of your class. But be aware, you are responsible for freeing this memory again, for example on destruction of your class instance.
- You pass the ownership to somebody else, who will take care of deallocation, when the memory is no longer needed.
The later case is usually needed, when an object/material/whatever gets inserted into a document. Obviously C4D needs to be the owner of such memory. Only C4D knows, when the user decides to delete the material. So by handing over the ownership of a piece of memory, you transfer the responsibility and permission to deallocate a piece of memory to another part of the program, in this case C4D's core.
doc->InsertMaterial(mymat); // <--- C4D owns it. You don't need to worry about freeing it yourself anymore.
I'd rather say the ownership gets transferred to C4D and you do not only need to worry, but also are not allowed to free this piece of memory yourself. C4D relies on this memory to stay intact until it decides on its own that it is no longer needed.
Ok, many words, but I hope, I was able to explain the concept of ownership of allocated memory.
Now, lets look at AutoAlloc.
Basically, AutoAlloc simply wraps the usual Alloc() call and stores the resulting pointer in a member variable. AutoAlloc owns the allocated object. In its destructor AutoAlloc will then use the usual Free() to deallocate the memory (it is allowed to as it owns the allocated object).
void noMemoryLeak()
{
AutoAlloc<BaseMaterial> mat;
return;
}
Now, there is no memory leak anymore. The AutoAlloc instance gets created on the stack. It allocates the BaseMaterial, stores the pointer internally and when the function is left, the AutoAlloc instance gets removed from the stack, its destructor gets called and the BaseMaterial object gets deallocated. Nice!
The only "magic" involved with AutoAlloc is, that it overloads the dereference operator ("->"), so that -> being used with the AutoAlloc object will result in dereferencing the internally stored pointer, which is basically the pointer to your BaseMaterial in above example.
Another bad example:
void myPersonalCrash(BaseDocument* doc)
{
AutoAlloc<BaseMaterial> mat(Mmaterial);
doc->InsertMaterial(mat);
return;
}
This will sooner or later lead to a crash. And I guess by now, you also know why. The material gets allocated and the AutoAlloc instance owns the pointed material. By inserting the material into the document, we tell C4D to take the ownership of that material. Then the function is left, the AutoAlloc instance gets destroyed, its destructor deallocates the material. And as soon as C4D tries to access the material, being of the opinion that it's owning this memory, it will crash (either by accessing already freed memory or by trying to free the memory a second time).
Solution:
void myPersonalCrash(BaseDocument* doc)
{
AutoAlloc<BaseMaterial> mat(Mmaterial);
doc->InsertMaterial(mat.Release());
return;
}
With Release() we tell the AutoAlloc instance to no longer care/be responsible/own the pointed memory. It will do nothing else, than setting its internal member variable to nullptr. So after calling Release() the ownership of the pointed object gets transferred to the caller of Release().
doc->InsertMaterial(mat.Release());
So in this line, mat.Release() transfers the ownership to you and you pass it directly on to C4D via InsertMaterial().
Now, one more pitfall:
void ohNoesAnotherCrash(BaseDocument* doc)
{
AutoAlloc<BaseMaterial> mat(Mmaterial);
doc->InsertMaterial(mat.Release());
mat->SetName("BOOM");
return;
}
This code is crashing, because during Release() the AutoAlloc instance sets its internal pointer member variable to nullptr. And by mat->SetName() this internal nullptr will be dereferenced. So if you need to work with an object/piece of memory formerly owned by an AutoAlloc, but by now no longer owned by the AutoAlloc due to calling Release(), then you need to store the returned pointer and use that one instead.
So either:
void yeahNoMoreCrash(BaseDocument* doc)
{
AutoAlloc<BaseMaterial> mat(Mmaterial);
BaseMaterial* bm = mat.Release();
doc->InsertMaterial(bm);
bm->SetName("silence");
return;
}
Or:
void stillNoCrash(BaseDocument* doc)
{
AutoAlloc<BaseMaterial> mat(Mmaterial);
mat->SetName("silence");
doc->InsertMaterial(mat.Release());
return;
}
One more thing on the benefits of AutoAlloc:
Usually you will have way more error exits in your code, than successful ones. And you usually transfer the ownership of an object/material only in the case of success.
Using normal Alloc, you have to take care not to miss any of the allocations in all your error exits, otherwise: memory leak. And yes, in the success case, when transferring the ownership, you don't need to do anything.
Now, with AutoAlloc it's exactly the other way round. You don't need to pay attention to all your error exits anymore, but instead you will need to pay attention to the one place, where you transfer the ownership in the successful case.
So AutoAlloc not only has the potential of reducing the lines of code you need to write, but it also reduces the amount of locations in your code, where you need to be careful and pay attention.
It's actually just a matter of using it a few times and you will get used to it and won't want to miss it anymore. I promise. And by the way, there's also AutoFree... but I hope with the above explanations AutoFree is no longer a secret anymore.
Ok, now I'll shut up. Sorry for the long post, but I really felt we needed to clarify some things. And I really hope, I was able to. If not, if there are any questions left on this topic, please ask. Memory allocation, ownership, leaks is one of the most important topics in development and we should leave no questions unanswered here. Of course you could also use DuckDuckGo to learn more about the concepts and you will probably find somebody who is way better in explaining this stuff than I am.
Edit 20:00 CET: Fixed wikipedia links for stack and callstack