Hi there! đ
Itâs a new day and a new issue of Bitesized Engineering, fresh out of the oven. But this time Iâm switching gears back to C++ & Memory Management (just like I promised!).
Last article of the Memory Management series discussed the Stack Attack, or - how attacks against Stack are usually done. And before that I spent quite some time discussing the Stacks (Stacks, heaps and other funny places, So, what is a Stack, really?, Why is Stack faster than Heap?, etc.). Now Iâd like to shift focus to Heaps and Heap Allocations.
But before we do that, I feel thereâs one important thing to answer - what happens when you type ânew Bar();â? Yes, we all know that it constructs an object on a heap, but how does that actually work? Thatâs what Iâm deemed to answer this time :)
As usual, the infographic goes first and then the more detailed explanation.
As you can see, what really happens when you write ânewâ statement (in C++ at least) is:
operator new gets called
placement new gets called
Now Iâd like you to stop and think for a second. You are a Computer. And somebody asks you to store some bits in your memory (remember the C++ and Data Types article? Itâs all just bits!). What would you do to accomplish that? Give it couple of seconds, Iâll wait.
.
.
.
Iâm guessing that one of the first things would be to ask - well, how MANY bits do you want to store? And then youâd proceed to find a place in memory that can house those bits. And once you do find that place - youâd probably PLACE the object there. And if you wanted to be OOP compliant, youâd also call a special method on the object, called âConstructorâ.
Guess what - C++ is no different really! Itâs exactly what it does as well. So if you say give me a new CandyBar();
, it will first go and figure the size of it (i.e. sizeof(CandyBar)
) and then it will go and ask OS where it can house those bits (in case of Windows, it would call HeapAlloc()). If OS says NO MEMORY FOR YOUR CANDY, MAâAM (e.g. your system is OUT OF MEMORY), then youâd probably want to throw an exception or something. But if there IS a memory, and OS gives you a memory location that can house your bits, then C++ proceeds and actually PLACES the object there (and calls a constructor).
And if you check definitions of operator new and placement new, youâd see this:
void* operator new ( std::size_t count );
and
::(optional) new (placement-params) new-type initializer
The first one is, you guessed it, operator new
, and itâs job is to find a place to house your bits, and the second one is placement new
, which actually CONSTRUCTS the object in the memory space returned by operator new
.
Putting it all together, it looks similar to this:
// Has to be included if you are using placement new
#include <new>
CandyBar* candy = new CandyBar("Snickers");
// could be re-written as:
void* storage = ::operator new(size_t(candy)); // operator new
CandyBar* candy = ::new(storage) CandyBar("Snickers"); // placement new
Couple of things to note here:
operator new
returns avoid*
. If youâre wondering what the heck is a âvoid pointerâ - it can literally be a pointer to ANYTHING. All you know is itâs a pointer to SOMETHING, but the rest you have no idea about.::
in front ofoperator new
is optional, but I added it for a reason, and the reason is that you might see it in a code and it could look scary. But itâs actually really simple - all it does is tell the compiler to useoperator new
from GLOBAL namespace (i.e. use the DEFAULToperator new
). This is useful in cases where you suspect someone might have overloaded it.new(storage) CandyBar(âSnickersâ)
is theplacement new
syntax. What it does is probably self-explanatory - it constructs theCandyBar
object at thestorage
address.
Why is all of this relevant, one might ask. And to tell you the truth - itâs probably not. It seems to be highly unlikely that you will ever need to write your own allocator. But itâs useful to know that itâs a viable option.
Speaking of overloading operator new
-
thatâs whatâs cool about it! You can actually overload operator new
on your Class; or for the whole app! And reasons could be various, but some might be:
Pre-allocating a pool of memory when process starts and doing all allocations from THAT pool, or
Ensuring that objects are created on a Stack and not a heap (i.e. you could return a Stack address from
operator new
)
Thereâs probably lot more, but this is all from the top of my head.
Finally, one common misconception worth mentioning is that there is a belief that "newâ creates objects on the Heap. That COULD be the case for other languages, but itâs certainly NOT the case for C++. Hereâs what C++ reference says about it:
[new expression] creates and initializes objects with dynamic storage duration, that is, objects whose lifetime is not necessarily limited by the scope in which they were created.
Source: cppreference.com
As you can see, it says NOTHING about Stack or Heap or what have you. All it says is that if you want a long-lived object whose lifetime is NOT tied to current scope - you should use ânewâ.
What actually happens is that in most (or all of?) the default implementations, this memory actually does come from the Heap. But it doesnât have to! From what I read (but never used) is that, technically speaking, you could return the memory address that is mapped to some I/O device (e.g. an SSD) and effectively construct your objects there.
Anyway, that would be about it for today :) If you enjoyed this article, do consider subscribing and sharing it with others.
In the meantime, here are the other articles from this series: