CSE 202: Computer Science II, Winter 2018
Dynamic memory management

You have been briefly introduced to new-expressions and delete-expressions in "Storage Durations", these expressions deal with objects located in dynamic storage. You should know that a new-expression creates an object that has dynamic storage duration and a delete-expression destroys an object that was created with a new-experssion. The primary reason to use new and delete is for implementing a container or data structure such as: std::vector, std::map, and std::list.

Allocation and deallocation

Let's dive deeper into what a new-expression is actually doing. A new-expression creates an object first by allocating storage for the object and then by initializing that storage. The storage that an object occupies is composed of a contiguous sequence of bytes; the unique address of the first byte of an object's storage is the memory location of that object.

All types of storage in C++ are composed of a contiguous sequence of bytes with each byte having a unique address (even across different types of storage). The particular type of storage that a new-expression allocates is dynamic storage, but the new-expression does not do this directly. The new-expression delegates the task of allocating dynamic storage to the global allocation functions: ::operator new and ::operator new[].

The global allocation functions only allocate dynamic storage; unlike the new-expressions, they do not initialize the dynamic storage that they allocate. To prove this, you may run the following program:

#include <iostream> using namespace std; struct C { C() { cout << '[' << this << "] C::C()" << endl; } ~C() { cout << '[' << this << "] C::~C()" << endl; } }; int main() { C* x1 = ::new C; cout << "x1 = " << x1 << endl; ::delete x1; C* x2 = (C*)::operator new(sizeof(C)); cout << "x2 = " << x2 << endl; ::operator delete(x2); return 0; }

The output will be something like:

[0x562aff849e70] C::C() x1 = 0x562aff849e70 [0x562aff849e70] C::~C() x2 = 0x562aff849e70

You can see here that the constructor for C was only called during the new-expression; ::operator new did not call the constructor for C. The delete-expression called the destructor for C, but the global deallocation function ::operator delete did not.

Also note that :: was used in front of the new and delete-expressions, it is possible to override the allocation and deallocation functions that new and delete-expressions use for a particular type; by explicitly using ::new and ::delete, the new and delete-expressions will use the global allocation functions provided by C++.

If you ever need to allocate storage without initializating it, consider using the global allocation and deallocation functions instead of the new and delete-expressions.

To allocate an uninitialized array, the ::operator new[] function should be used:

int* p = (int*)::operator new[](100 * sizeof(int));

The code above allocates an uninitialized array of 100 int and stores a pointer to this array in p. To deallocate an array that was allocated with ::operator new[], you must call ::operator delete[] like: ::operator delete[](p).

Construction and destruction

It is possible to manually initialize an object, whether or not it has already been initialized:

#include <iostream> using namespace std; int main() { int x = 42; cout << "x: " << x << endl; ::new(&x) int; cout << "x after default-initialization: " << x << endl; ::new(&x) int(); cout << "x after value-initialization: " << x << endl; return 0; }

The output of the above program is:

x: 42 x after default-initialization: 42 x after value-initialization: 0

The new-expression used in the form of ::new(&x) int; default-initializes an already allocated object located at &x. ::new(&x) int(); value-initializes an already allocated object located at &x.

The output of this program shows that default-initialization does nothing for non-class types while value-initialization zero-initializes a non-class-type object by setting all the bytes in that object's storage to zero.

The following program demonstrates the manual allocation, construction, destruction, and deallocation of a class-type object:

#include <iostream> using namespace std; struct T { int x; T(int x) { cout << "T::T()" << endl; this->x = x; } ~T() { cout << "T::~T()" << endl; } }; int main() { T* p = (T*)::operator new(sizeof(T)); ::new(p) T(42); cout << "p->x: " << p->x << endl; p->T::~T(); ::operator delete(p); return 0; }

The code in the main function does the same as if the code was:

T* p = ::new T(42); cout << "p->x: " << p->x << endl; ::delete p;

The statement ::new(p) T(42) direct-initializes an object pointed to by p with the value 42. Since the object is a class-type, it's constructor will be called with 42 as the argument.

The statement p->T::~T() calls the destructor of an object of type T through it's pointer p; a statement like this can only be used on class-type objects unless you are writing code for a template.

Summary
# Expression Meaning
1 (T*)::operator new(sizeof(T)) Allocates, from dynamic storage, an uninitialized object of type T and returns a pointer to it
2 (T*)::operator new[](N * sizeof(T)) Allocates, from dynamic storage, an array of N uninitialized objects of type T and returns a pointer to the first element.
3 ::new(p) T Default-initializes an object of type T pointed to by p. For class-types, the constructor gets called; for non-class-types, nothing happens
4 ::new(p) T() Value-initializes an object of type T pointed to by p. For class-types, the constructor gets called; for non-class-types, the object gets zero-initialized.
5 ::new(p) T(x) Direct-initializes an object of type T pointed to by p. For class-types, the appropriate constructor gets called; for non-class-types, the value gets assigned after any necessary conversion.
6 ::new T Performs expressions 1 and 3
7 ::new T() Performs expressions 1 and 4
8 ::new T(x) Performs expressions 1 and 5
9 ::new T[N] Performs expression 2, then performs expression 3 on each element
10 ::new T[N]() Performs expression 2, then performs expression 4 on each element
11 p->T::~T() Calls the destructor of an object, of type T, pointed to by p.
12 ::operator delete(p) Deallocates the object pointed to by p which was previously allocated using expression 1.
13 ::operator delete[](p) Deallocates the array pointed to by p which was previously allocated using expression 2.
14 ::delete p If the object pointed to by p has a destructor, performs expression 11; then performs expression 12
15 ::delete[] p If the elements in the array pointed to by p have destructors, performs expression 11 on each element; then performs expression 13.