Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C++ Pointers and Dynamic Memory Management


November 1995/C++ Pointers and Dynamic Memory Management

reviewed by Chuck Allison


Chuck Allison is a former columnist with CUJ and a Senior Software Engineer in the Information and Communication Systems Department of the Church of Jesus Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in mathematics, has been programming since 1975, and has been teaching and developing in C since 1984. His current interest is object-oriented technology and education. He is a member of X3J16, the ANSI C++ Standards Committee. Chuck can be reached on the Internet at [email protected].

Pointers and dynamic memory are undoubtedly the most powerful constructs of the C language, yet also the most difficult concepts to master. C++ only complicates these concepts, so you'd better be prepared before you tread the deep waters of object-oriented programming. This book offers some preparation, as a fairly complete treatise on C pointers, as well as a good introduction to the new features of pointers in C++. It is actually a sequel to the author's C Pointers and Dynamic Memory Management. Although I'm not familiar with the prequel, I would imagine that much of it is repeated here, since at least half the book covers pointers from the C perspective, making it quite self-contained. The book targets beginning to moderately-experienced programmers who are familiar with the syntax and basic constructs of C and C++. A companion diskette contains the code shown in the book.

Topics Covered Reasonably Well

The book's most striking contribution is probably its 3-dimensional container analogy, used to depict pointers to data elements. Most authors, myself included, employ the usual 2-d notation, as in

for the declarations

int i = 7;
int *ip = &i;
The 3-d metaphor is especially effective when a referenced object spans more than one byte, such as a long integer: the outside of the "container" has demarcations for four bytes, but the inside holds a single value. This clearly shows that pointers point to the beginning of an object. With that understanding, readers can easily see why they can't dereference a pointer to void without a cast, as Daconta explains on page 36:

"So the compiler has a starting point; big deal! It cannot use just a starting point. It needs to know where to finish. In other words, it needs to know how many bytes of data to get."

Since this is also a book about memory management, the author thoroughly investigates the three types of memory: static (which he calls global), automatic (i.e., the stack), and dynamic (or heap memory). The book contains a good discussion of stack frames for those interested in the details of how functions are called and arguments are passed.

The ensuing discussion explains that there are basically two variations of storage class for any identifier: static or automatic. (Even the pointers that refer to dynamic memory fall into one of these categories). Daconta then erroneously identifies const and volatile as storage class specifiers. These two keywords are actually type specifiers, and have nothing to do with a variable being automatic or static. The storage class specifiers in C++ are:

auto
register
static
extern
mutable
mutable is a relatively new C++ keyword (1992) which applies only to class data members, and const behaves a little differently than in C, but Daconta explains neither of these facts anywhere.

Daconta does a good job explaining multi-dimensional arrays, which actually are not defined in either language. C++ uses without change C's notion of "arrays of arrays." For example, the declaration

int a[3][4];
defines a as an "array of 3 arrays of 4 integers." To define a pointer that behaves identically to a in expressions, you do not do this:

int **p = a;
A good compiler will even give you a warning about incompatible pointer types in that expression. Since the first element of the array a is an "array of 4 integers," you must declare p as a pointer to such, as in

int (*p)[4] = a;
Now p[i][j] is equivalent to a[i][j].

The book also includes a chapter on traditional data structures, such as linked lists and hash tables. These are excellent vehicles for illustrating both pointers and heap memory and how they work together. The author also uses templates in this chapter, which is the preferred way to implement homogeneous containers in C++.

He has sprinkled a few "OOP Warnings" and other guidelines throughout the book to alert the reader to common pitfalls. For example, after an example that uses a global variable in an illustration of the various types of scope in C, he says:

"OOP WARNING: The Source 2.2 demonstration of globals was just a demonstration and not a recommendation. Globals should be avoided in object-oriented programming because they provide no protection for data. Thus they violate the entire purpose of encapsulation."

I would insert a "usually" before the phrase "be avoided," but the idea is sound.

In explaining function pointers, Daconta either isn't aware of the simpler alternate syntax, or he doesn't like it. For example, in a typical generic sorting function, he calls a compare function via dereferencing:

void isort(int (*cmp)(char *, char *), char ** date, int length)
{
   ...
   ... (*cmp)(data[j], data[j-1]) ...
   ...
}
The ANSI C committee decided long ago that the explicit dereferencing operation is redundant, since the only thing you can do with a function pointer in a function-call context is invoke the function. Therefore, you can render the call more readably as

cmp(data[j], data[j-1])
which is superior to the former version just like a[i] is more readable than *(a + i).

The last two chapters delve into the mysteries of memory management. Chapter 14 implements a debugging heap allocator, while Chapter 15 explores the joys of Intel's segmented x86 architecture, which hopefully will become a non-issue in today's emerging 32-bit world.

Coverage of C++

As far as C++-specific issues go, the book covers the new and delete operators; the interaction of constructors, assignment operators, and destructors; the this pointer; abstract classes; polymorphism; and the inner-workings of virtual functions. That's the good news. My greatest disappointment in this book is its incomplete coverage of C++ (which supposedly is the reason for the book's existence in the first place). When the author tries to go beyond the basics, he is a man of few words and not a few misconceptions.

A common point of confusion for novices is the copy constructor. Exactly when is it called? It is only partially correct to say "a copy constructor is called when passing an object to a function," which is essentially what Daconta says on page 63:

"It is important to understand that the copy constructor is specific to the passing of objects to functions."

This statement can be misleading. The copy constructor is used to initialize one object from another of the same type. What's important to understand is that objects passed to or returned from a function by value are done so by initialization, which means that a constructor (not necessarily the copy constructor) is called. For example, if I have a class Foo with a single-integer-argument constructor, Foo::Foo(int), and a function f with the following signature:

void f(const Foo& x);
then the call

f(2);
does not call the copy constructor, but instead passes Foo(2) as a temporary object to f. Furthermore, if a function g returns a Foo object, it may or may not use the copy constructor. If g contains the statement

return 2;
it will again use Foo(2). You will not come to thoroughly understand initialization or the proper role of the copy constructor from this book.

Furthermore, the copy constructor is usually defined with a const reference argument, as in

Foo(const Foo&);
whereas the author always uses

Foo(Foo&);
which is valid C++, but semantically incomplete, since there's rarely a need to modify the passed object. This reveals Daconta's lack of appreciation for the role of const in C++. He doesn't even use it until page 264, long after the naive reader has picked up some bad habits. In fact, nowhere in the book does he even mention const member functions. Like many unwary C++ programmers, he falls prey to the operator[] problem. In his "safe" String class, he defines

char& operator[](int index)
{
   assert(index < _sz);
   assert(index >= 0);
   return _storage[index];
}
Now what happens if I have a const String object? I cannot even call this function, since it is not a const member function. For this reason, experienced C++ programmers always define two versions of operator[], in this case

char& operator[](int);
char operator[](int) const;
The general practice for any type T is:

T& operator[](int);
const T& operator[](int) const;
An early introduction of const would also simplify the explanation of the this pointer. In a typical C++ implementation, every non-const, non-static member function of class T receives a hidden parameter called this with the following declaration:

T * const this;
which means that the value of this itself cannot be modified. For const, non-static functions it is declared like this:

const T * const this;
which prevents you from altering the value of any non-static data members. These are not new concepts here, just normal C-language pointer access rules.

Yet sometimes you need to override the const-ness of a member function to alter some implementation-specific data member so you "cast away" const, as in

T *This = (T*) this;
This->x = ... // override const
or better yet, use the language operator made for this very purpose:

const_cast<T*>(this)->x == ...
C++ provides four new cast operators for handling pointers:

dynamic_cast
static_cast
const_cast
reinterpret_cast
These operators were voted into the standard over two years ago, and have been available in Borland C++ for some time. It's too bad that the author doesn't know about them, but even worse that he doesn't use const as the designers of C++ intended.

There is another time bomb lurking in his "safe" String class. If he should ever add a binary operator, such as operator+ for concatenation, his class will fail on an expression such as

String s;
s + "hello";
Why? Because he has provided implicit conversions both to and from char *, via the following member functions:

// This should be const char *,
// of course
String(char *instr);
// Better if this is operator const
// char*, too
operator char*();
You can't do mixed-mode operations such as s + "hello" when both conversions are available, because the compiler doesn't know which one to pick: should it convert s to char *, or "hello" to String? The convention is to favor conversions from simpler to more complex types, so he should keep the single-argument constructor here, and provide an explicit conversion to char *. For example, he could replace the conversion operator with a member function having a suggestive name, such as

const char* to_cstr() const; // Note the usage of const
and use it like any other member function:

printf("%s",s.to_cstr());
Throughout the book Daconta checks for a null pointer to see if the new operator failed to allocate memory. The standards committee determined long ago that the compiler should throw exceptions when memory is exhausted. I don't think the word "exception" appears anywhere in the book (at least not in the index), and his examples will not work on a standard-conforming compiler. To his credit, he does mention the set_new_handler function, which allows the programmer to regain control when new fails, but his example prints error messages to cerr, which is likely to require more of the already-exhausted heap.

Daconta also doesn't know about operator new[] and operator delete[], for allocating and deallocating array memory, both on the global or class level (again, Borland C++ has supported them for some time). He states on page 192:

"When allocating an array of objects X, the global operator will be used, even if a class operator exists."

That's just plain wrong. He even goes so far as to disassemble a program to discover that his compiler uses a different allocator for arrays. If he only knew about operator new[], he could save himself and his readers a lot of head scratching. (For an illustration of the semantics of these special functions, see Listing 23 - Listing 25 of my former column, "Code Capsules: Dynamic Memory Management, Part 2," CUJ, November 1994.)

Daconta's treatment of placement new is even more puzzling. He gives it only these few lines:

"You can pass additional arguments to new using placement syntax. A very common example of this is to initialize objects at a specified address:

void *operator new(size_t, void *p)
{return p;}"
His "example" is not an example at all, but the canonical implementation for the single-address placement new. If he had used a less trivial example to illustrate class-based operator new(), he would've found a use for it. Placement new was invented to accommodate per-class allocators, which save significantly on standard heap allocator overhead. See my article quoted above for such an example.

Another concept that often taxes readers imaginations is the virtual base class; no treatment of inheritance in C++ is complete without it. Since the obvious implementation is via pointers, it makes sense for this book to cover the topic. It does not.

Perhaps the biggest embarrassment of all is Daconta's treatment of class member pointers. When I read the back cover, I was excited when I saw this item from a bulleted-list:

  • Covers all aspects of pointers including: pointer pointers, function pointers, and even class member pointers.
I thought, "Great! Someone is finally going to write something substantial about member pointers!" Imagine my disappointment when I found only 15 text lines, which in Daconta's wordy style don't say a lot. All he mentions is that you can define pointers to data members, for example, for a class age, he defines a pointer to a data member, a, like this:

// requires public data member a
int age::* aap = &age::a;
He then qualifies the example by saying that you wouldn't really want to use pointers to data members anyway, since data should be encapsulated. That's it.

Well of course you wouldn't want to do that! Member pointers were invented mainly to get at member functions, which he should have illustrated. See the second edition of Stroustrup's The C++ Programming Language, page 167 for a canonical example. Member pointers come in handy in software tools when the actual member function that needs to be called is not known at compile time, like callbacks in a windowing system. You can see an example of this sort in the second edition of Stan Lippman's C++ Primer, pp. 250-256.

Summary

Giving credit where credit is due, this is a very good book for pointers and dynamic memory management in C. It also nicely covers the basics of the new and delete operators, and inheritance and polymorphism in C++. If the author knew as much about C++ as he does about the internals of memory management or about Intel's x86 architecture, this would have been a great book. Beyond learning the basics, I'm afraid that novice programmers reading this book might pick up some bad habits and erroneous ideas. You might consider waiting for the second edition.

Title: C++ Pointers and Dynamic Memory Management
Author: Michael C. Daconta
Publisher: Wiley - QED
Pages: 464
Price: US$ 39.95
ISBN: 0-471-04998-0


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.