C++14 introduced "sized" versions of operator delete
, i.e.
void operator delete( void* ptr, std::size_t sz );
and
void operator delete[]( void* ptr, std::size_t sz );
Reading through N3536, it seems that those operators were introduced to increase performance. I know that the typical allocator used by operator new
"stores" the size of the bulk memory somewhere, and that's how typical operator delete
"knows" how much memory to return to the free store.
I am not sure however why the "sized" versions of operator delete
will help in terms of performance. The only thing that can speed things up is one less read operation regarding the size from the control block. Is this indeed the only advantage?
Second, how can I deal with the array version? AFAIK, the size of the allocated array is not simply sizeof(type)*number_elements
, but there may be some additional bytes allocated as the implementation may use those bytes as control bytes. What "size" should I pass to operator delete[]
in this case? Can you provide a brief example of usage?
delete is used for one single pointer and delete[] is used for deleting an array through a pointer.
When delete is used to deallocate memory for a C++ class object, the object's destructor is called before the object's memory is deallocated (if the object has a destructor). If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.
C++ supports dynamic allocation and deallocation of objects using the new and delete operators. These operators allocate memory for objects from a pool called the free store (also known as the heap).
Overloading New and Delete operator in c++ The new and delete operators can also be overloaded like other operators in C++. New and Delete operators can be overloaded globally or they can be overloaded for specific classes.
Dealing with your second question first:
If present, the std::size_t size argument must equal the size argument passed to the allocation function that returned ptr.
So, any extra space that might be allocated is the responsibility of the runtime library, not the client code.
The first question is more difficult to answer well. The primary idea is (or at least seems to be) that the size of a block often isn't stored right next to the block itself. In most cases, the size of the block is written, and never written again until the block is deallocated. To avoid that data polluting the cache while the block is in use, it can be kept separately. Then when you go to deallocate the block, the size will frequently have been paged out to disk, so reading it back in is quite slow.
It's also fairly common to avoid explicitly storing the size of every block explicitly at all. An allocator will frequently have separate pools for different sizes of blocks (e.g., powers of 2 from 16 or so up to around a couple kilobytes or so). It'll allocate a (fairly) large block from the OS for each pool, then allocate pieces of that large block to the user. When you pass back an address, it basically searches for that address through the different sizes of pools to find which pool it came from. If you have a lot of pools and a lot of blocks in each pool, that can be relatively slow.
The idea here is to avoid both of those possibilities. In a typical case, your allocations/deallocations are more or less tied to the stack anyway, and when they are the size you're allocating will likely be in a local variable. When you deallocate, you'll typically be at (or at least close to) the same level of the stack as where you did the allocation, so that same local variable will be easily available, and probably won't be paged out to disk (or anything like that) because other variables stored nearby are in use as well. For the non-array form, the call to ::operator new
will typically stem from a new expression
, and the call to ::operator delete
from the matching delete expression
. In this case, the code generated to construct/destroy the object "knows" the size it's going to request (and destroy) based solely on the type of object being created/destroyed.
For the size
argument to C++14 operator delete
you must pass the same size you gave to operator new
, which is in bytes. But as you discovered it's more complicated for arrays. For why it's more complicated, see here: Array placement-new requires unspecified overhead in the buffer?
So if you do this:
std::string* arr = new std::string[100]
It may not be valid to do this:
operator delete[](arr, 100 * sizeof(std::string)); # BAD CODE?
Because the original new
expression was not equivalent to:
std::string* arr = new (new char[100 * sizeof(std::string)]) std::string[100];
As for why the sized delete
API is better, it seems that today it is actually not but the hope is that some standard libraries will improve performance of deallocation because they actually do not store the allocation size next to each allocated block (the classical/textbook model). For more on that, see here: Sized Deallocation Feature In Memory Management in C++1y
And of course the reason not to store the size next to every allocation is that it is a waste of space if you don't truly need it. For programs which make many small dynamic allocations (which are more popular than they ought to be!), this overhead can be significant. For example in the "plain vanilla" std::shared_ptr
constructor (rather than make_shared
), a reference count is dynamically allocated, so if your allocator stores the size next to it, it might naively require about 25% overhead: one "size" integer for the allocator plus the four-slot control block. Not to mention memory-pressure: if the size is not stored next to the allocated block, you avoid loading a line from memory on deallocation--the only information you need is given in the function call (well, you also need to look at the arena or free-list or whatever, but you needed that in any case, you still get to skip one load).
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