Channels ▼
RSS

Design

Deallocating Objects vs. Deallocating Storage


When a program allocates an object, it not only allocates storage for the object but also initializes that storage with a value appropriate for the type of object. When a program just allocates storage, it leaves that storage uninitialized. Calling storage "raw" is a common way to emphasize that it's not initialized.

The distinction between storage and objects is readily apparent in the behavioral difference between the Standard C malloc function and a C++ new-expression. Specifically, a C (or C++) expression such as:

pw = (widget *)malloc(sizeof(widget));

allocates raw storage. Indeed, the malloc call allocates storage that's big enough and suitably aligned to hold an object of type widget, but it leaves that storage uninitialized. In contrast, a C++ new-expression as in:

pw = new widget ();

creates a widget object with a coherent initial value.

The distinction between storage and objects carries over when allocating array objects. A call to malloc as in:

pw = (widget *)malloc(10 * sizeof(widget));

allocates raw storage for an array of 10 widgets. By contrast, the C++ array new-expression in:

pw = new widget [10];

allocates an array of 10 properly initialized widgets.

Each new-expression is conceptually, if not actually, a two-step process: (1) allocate storage for an object, and (2) initialize it. For objects of class types, initializing an object usually involves calling a constructor. An array new-expression is also a two-step process, but the second step is a loop that initializes every array element.

It should come as no surprise that the distinction between objects and raw storage also comes into play when you deallocate them. Just as allocating an object in C++ may involve calling a constructor, deallocating an object may involve calling a destructor.

In this column, I explain how delete-expressions interact with destructors and deallocation functions in C++. I'll also explain how C programmers can employ a style of memory deallocation that mimics the behavior of C++ delete-expressions.

Delete-Expressions and Destructors

In C++, a destructor is a special class member function that "destroys" objects of that class type. Generally, to destroy an object means to deallocate or release the resources used by that object. A destructor's function name is always the same as its class name, but starts with a ~ (tilde, or "squiggle" if you prefer) as in:

 class widget { 
     public:     widget();        // a constructor     
	             ~widget();       // a destructor     
				 // ...
};

Just as constructors provide guaranteed initialization for class objects, destructors provide guaranteed destruction. Just as you don't write explicit calls to constructors — compilers generate them for you — you don't write explicit destructor calls either. (Actually, you can write explicit destructor calls, but it's something you should avoid doing if possible.) Whenever you define an object with a class type, the compiler automatically plants calls to the object's destructor at the point(s) in the program where that object's lifetime ends — typically at function return statements and at the end (the closing brace) of function bodies and statement blocks.

For guaranteed destruction to really be guaranteed, the compiler must generate a call to a destructor at every point where the program destroys an object, including in delete-expressions. Thus, if pw is a pointer to a single widget, a delete-expression such as:

 delete pw;

doesn't just deallocate storage for that widget; it applies widget's destructor to the object addressed by pw to release any resources that the widget may have in its possession.

Default initialization for scalar types, such as int and double, does nothing. When you dynamically allocate an object of scalar type, as in:

 int *pi = new int;

the allocated object remains uninitialized. Default destruction for scalar types does nothing as well. An object of scalar type consumes no resources beyond its own storage, so there's nothing for its "destructor" to do. Thus, a delete-expression such as:

 delete pi;

just deallocates the storage for the int addressed by pi. As I explained in an earlier article, a destructor doesn't deallocate the storage for an object. It releases the resources managed by the object, some of which may be storage. The storage for the object being destroyed will be deallocated by some other runtime agent of the program. For example, for a mythical widget object declared local to a function, the destructor for that widget executes just before the function returns. The destructor releases the widget's resources, and then the runtime system deallocates the widget's storage by popping it off the stack along with the rest of the function's stack frame.

Just as the array new-expression in:

 pw = new widget [10];

applies the default widget constructor to each element in the storage allocated for the array, an array delete-expression such as:

 delete [] pw;

applies the widget destructor to each array element. Whereas an array new-expression applies the constructors to the array element in ascending order by subscript starting at the 0th element, an array delete-expression applies the destructors in the reverse order.

Delete-Expressions and Operator Delete

Just as a new-expression allocates memory by calling a function named operator new (rather than malloc), a delete-expression deallocates memory by calling a function named operator delete (rather than free). Each C++ environment provides a default implementation for a global operator delete, declared in the standard header <new> as:

 void operator delete(void *p) throw ();

The empty exception specification:

 throw ()

at the end of the function heading indicates that operator delete isn't supposed to allow any exceptions to propagate from the function. That is, operator delete may throw exceptions of various types, but it will catch those other exceptions before they can escape to the calling environment. However, an empty exception specification doesn't really guarantee that no exceptions will propagate.

If pw is a pointer to an object of class type widget, a delete-expression such as:

 delete pw;

translates more or less into something like:

if (pw != NULL) {
    pw->~widget();     
	operator delete(pw);
}

A delete-expression applied to a null pointer does nothing. If the pointer is non-null, the delete-expression applies the destructor to the soon-to-be-deleted object, and then deallocates the object's storage by passing the object's address to operator delete. (That first statement inside the body of the if-statement — an explicit destructor call — is something you can actually write in C++ but really shouldn't.)

Only class types have destructors, so if p is a pointer to an object of non-class type, then a delete-expression such as:

 delete p;

translates into just:

 operator delete(p);

In this case, the compiler need not generate the surrounding if-statement to guard against a null pointer; operator delete accepts null pointer arguments.

An array delete-expression uses a different function with the similar name operator delete [], pronounced as "operator delete square bracket" or just "operator delete bracket". The C++ standard refers to any function named either operator delete or operator delete [] as a deallocation function.

Each C++ environment provides a default implementation for a global operator delete [], declared as:

 void operator delete [](void *p) throw ();

This function declaration is identical to that for operator delete, except for the function name. Using separate deallocation functions for arrays and non-arrays objects lets you use different memory management policies for each.

As mentioned earlier, the second step of an array delete-expression that deletes an array is a loop which applies the destructor to each array element in descending order by subscript (or address). For example, if widget is a class type and pw is a pointer to a widget, then the array delete-expression:

 delete [] pw;

translates more or less into something like:

 if (pw != NULL) {     
     widget *p = pw + N;     
	 while (p != pw)         
	     (--p)->~widget();     
    operator delete [](pw); 
 }

The while-loop applies the destructor to each widget in that array of widgets, from the last element to the first. The subsequent call to operator delete [] deallocates the storage for the entire array.

In the code just above, N represents the array dimension (the number of elements in the array). Where, you ask, did that come from? As I explained in an earlier column, operator new [] often allocates an extra word or two in addition to the storage required for the array elements. It places the array dimension (or some information from which to compute the array dimension) into that extra storage so that operator delete [] can find it. That's where N comes from.

Again, only class types have destructors, so if p is a pointer to an array with elements of non-class type, then an array delete-expression such as:

 delete [] p;

translates into just:

 operator delete [](p);

You can approximate classes with constructors by using structs and functions. You can easily extend the model to include destructors. For example, you can implement a C++ widget class as a C struct:

 typedef struct widget widget; 
 struct widget {     
     // widget data members go here 
 };

with a "destructor" implemented as a C function:

 void widget_destroy(widget *w);

You can mimic the behavior of a C++ delete-expression by using a single inline function:

 inline void delete_widget(widget *w) {
     if (w != NULL) {    
	     widget_destroy(w);          
	     free(w);       
     } 
 }

Then, if pw points to a dynamically allocated widget, you can delete it by calling:

 delete_widget(pw);

which is a pretty fair approximation for the C++ delete- expression:

 delete pw;

Extending the approach to deleting arrays of widgets is a bit harder. You must augment the function that allocates the array to squirrel away the array dimension so that the delete function can find it. I'll show you the details in an upcoming column.


Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. This article is adapted from an article that appeared in 2009 in EE Times.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video