Using C++ as better CUSENIX

 

mccluskey, glen by Glen McCluskey
<glenm@glenmccl.com>

Glen McCluskey is a consultant with 15 years of experience and has focused on programming languages since 1988. He specializes in Java and C++ performance, testing, and technical documentation areas.

In this column we'll look at C++ replacements for malloc() and free(), the operators new() and delete().

To give an example of how these are similar and how they differ from their C counterparts, suppose that we want to allocate a 100-long vector of integers for some purpose. In C, we would say:

int* ip;
ip = (int*)malloc(sizeof(int) * 100);
...
free((void*)ip);

With new/delete in C++, we would have:

int* ip;
ip = new int[100];
...
delete [] ip;

The most obvious difference is that the C++ approach takes care of the low-level details necessary to determine how many bytes to allocate. With the C++ new operator, you simply describe the type of the desired storage, in this example int[100].

The C and C++ approaches have several similarities:

  • Neither malloc() nor new initializes the space to zeros.
  • Both malloc() and new return a pointer that is suitably aligned for a given machine architecture.
  • Both free() and delete do nothing with a NULL pointer.

malloc() returns NULL if the space cannot be obtained. Many versions of new in existing C++ compilers do likewise. However, the draft ANSI C++ standard says that a failure to obtain storage should result in an exception being thrown or should result in the currently installed new handler being invoked. I assume that NULL is returned.

new Handlers

The idea of a new handler can be illustrated as follows:

extern "C" int printf(const char*,
...); extern "C" void exit(int);

typedef void (*new_handler)(void);
new_handler set_new_handler(new_handler);
void f()
{
  printf("new handler invoked due to new failure\n");
  exit(1);

}
int main()
{
  float* p;
  set_new_handler(f);
  for (;;)
    p = new float[5000]; // something that will
                           // fail eventually
  return 0;
}

A new handler is a way of establishing a hook from the C++ standard library to a user program. set_new_handler() is a library function that records a pointer to another function that is to be called in the event of a new failure.

Deleting Arrays

Note that saying:

delete ip;

instead of:

delete [] ip;

will work with some compilers in the example above.

This is an area of C++ that has changed several times in recent years. There are a number of issues to note. The first is that new and delete in C++ have more than one function. The new operator allocates storage, just like malloc() in C, but it is also responsible for calling the constructor for any class object that is being allocated. For example, if we have a String class, saying:

String* p = new String("xxx");

will allocate space for a String object and then call the constructor to initialize the String object to the value "xxx." In a similar way, the delete operator arranges for the destructor to be called for an object, and then the space is deallocated in a manner similar to the C function free().

If we have an array of class objects, as in:

String* p = new String[100];

then a constructor must be called for each array slot, because each is a class object. Typically, this processing is handled by a C++ internal library function that iterates over the array.

In a similar way, deallocation of an array of class objects can be done by saying:

delete [] p;

It used to be that you had to say:

delete [100] p;

but this feature is obsolete. The size of the array is recovered by the library function that implements the delete operator for arrays. The pointer/size pair can be stored in an auxiliary data structure, or the size can be stored in the allocated block before the first actual byte of data.

What makes this a bit tricky is that all this work of calling constructors and destructors doesn't matter for fundamental data types like int:

int* ip;
ip = new int[100];
delete ip;

This code will work in many cases because there are no destructors to call, and deleting a block of storage works pretty much the same whether it's treated as an array of ints or a single large chunk of bytes.

But more recently, the ANSI Standardization Committee decided to break out the new and delete operators for arrays as separate functions so that a program can control the allocation of arrays separately from other types. For example, you can say:

void* operator new(unsigned int) {/* ... */ return 0;}
void* operator new[](unsigned int) {/* ... */ return 0;}
void f()
{
  int* ip;
  ip = new int; // calls operator new()
  ip = new int[100]; // calls operator new[]()
}

and the appropriate functions will be called in each case. This is kind of like defining your own versions of the malloc() and free() library functions in C.

Defining Your Own new/delete Functions

It is possible to define your own new and delete functions. For example:

void* operator new(size_t s)
{
  // allocate and align storage of size s
  // handle failure via new_handler or exception
  // return pointer to storage
}
void operator delete(void* p)
{
  // handle case where p is NULL
  // handle deallocation of p block in some way
}

size_t is a typedef, typically defined to mean "unsigned int." It's found in a header file that may vary between compiler implementations.

 

?Need help? Use our Contacts page.
First posted: 4th February 1998 efc
Last changed: 423 February 1998 efc
Issue index
;login: index
USENIX home