Next Previous Contents

9. Memory Allocation in C++

In C, you use malloc(), free() and variants of malloc() to allocate and free memory, but these functions have their pitfalls. Therefore C++ introduced operators for handling memory, these operators are called new and delete. These operators allocates and frees memory from the heap (or sometimes called the free store) at runtime.

In C++, you should always use new and delete unless you're really forced to use malloc() and free(). But be aware that you cannot mix the two. You cannot malloc() memory, and then delete it afterwards, likewise you can't "new" memory, and then free it with free().

9.1 C++ Zap (Delete) function

The delete and new operators in C++ are much better than the malloc and free functions of C. Consider using new and zap (delete function) instead of malloc and free as much as possible.

To make delete operators even more cleaner, make a Zap() inline function. Define a zap() function like this:


// Put an assert to check if x is NULL, this is to catch
// program "logic" errors early. Even though delete works 
// fine with NULL by using assert you are actually catching 
// "bad code" very early

// Defining Zap using templates
// Use zap instead of delete as this will be very clean
template <class T>
inline void zap(T & x)
{
        {assert(x != NULL);}
        delete x;
        x = NULL;
}

// In C++ the reason there are 2 forms of the delete operator is - because
// there is no way for C++ to tell the difference between a pointer to
// an object and a pointer to an array of objects. The delete operator
// relies on the programmer using "[]" to tell the two apart.
// Hence, we need to define zaparr function below.
// To delete array of pointers
template <class T>
inline void zaparr(T & x)
{
         {assert(x != NULL);}
     delete [] x;
     x = NULL;
}

The zap() function will delete the pointer and set it NULL. This will ensure that even if multiple zap()'s are called on the same deleted pointer then the program will not crash. Please see the function zap_example() in example_String.cpp click on 'Source code of C++'.


        // See zap_example() in example_String.cpp
        zap(pFirstname);
        //zap(pFirstname); // no core dumps.  Because pFirstname is NULL now
        //zap(pFirstname); // no core dumps.  Because pFirstname is NULL now

        zap(pLastname);
        zap(pJobDescription);

        int *iiarray = new int[10];
        zaparr(iiarray);

There is nothing magical about this, it just saves repetetive code, saves typing time and makes programs more readable. The C++ programmers often forget to reset the deleted pointer to NULL, and this causes annoying problems causing core dumps and crashes. The zap() takes care of this automatically. Do not stick a typecast in the zap() function -- if something errors out on the above zap() function it likely has another error somewhere.

Also my_malloc() , my_realloc() and my_free() should be used instead of malloc(), realloc() and free(), as they are much cleaner and have additional checks. For an example, see the file "String.h" which is using the my_malloc() and my_free() functions.

WARNING : Do not use free() to free memory allocated with 'new' or 'delete' to free memory allocated with malloc. If you do, then results will be unpredictable.

See the zap examples in example_String.cpp click on 'Source code of C++'.

9.2 Usage of my_malloc and my_free

Try to avoid using malloc and realloc as much as possible and use new and zap(delete). But sometimes you may need to use the C style memory allocations in C++. Use the functions my_malloc() , my_realloc() and my_free(). These functions do proper allocations and initialisations and try to prevent memory problems. Also these functions (in DEBUG mode) can keep track of memory allocated and print total memory usage before and after the program is run. This tells you if there are any memory leaks.

The my_malloc and my_realloc is defined as below. It allocates little more memory (SAFE_MEM = 5) and initializes the space and if it cannot allocate it exits the program. The 'call_check(), remove_ptr()' functions are active only when DEBUG_MEM is defined in makefile and are assigned to ((void)0) i.e. NULL for non-debug production release. They enable the total-memory used tracing.


void *local_my_malloc(size_t size, char fname[], int lineno) 
{
        size_t  tmpii = size + SAFE_MEM;
        void *aa = NULL;
        aa = (void *) malloc(tmpii);
        if (aa == NULL)
                raise_error_exit(MALLOC, VOID_TYPE, fname, lineno);
        memset(aa, 0, tmpii);
        call_check(aa, tmpii, fname, lineno);
        return aa;
}

char *local_my_realloc(char *aa, size_t size, char fname[], int lineno)
{
        remove_ptr(aa, fname, lineno);
        unsigned long tmpjj = 0;
        if (aa) // aa !=  NULL
                tmpjj = strlen(aa);
        unsigned long tmpqq = size + SAFE_MEM;
        size_t  tmpii = sizeof (char) * (tmpqq);
        aa = (char *) realloc(aa, tmpii);
        if (aa == NULL)
                raise_error_exit(REALLOC, CHAR_TYPE, fname, lineno);

        // do not memset memset(aa, 0, tmpii);
        aa[tmpqq-1] = 0;
        unsigned long kk = tmpjj;
        if (tmpjj > tmpqq)
                kk = tmpqq;
        for ( ; kk < tmpqq; kk++)
                aa[kk] = 0;
        call_check(aa, tmpii, fname, lineno);
        return aa;
}

See my_malloc.cpp. and the header file my_malloc.h. for full implementation of the my_malloc program.

An example on usage of my_malloc and my_free as below:


        char    *aa;
        int     *bb;
        float   *cc;
        aa = (char *) my_malloc(sizeof(char)* 214);
        bb = (int *) my_malloc(sizeof(int) * 10);
        cc = (float *) my_malloc(sizeof(int) * 20);

        aa = my_realloc(aa, sizeof(char) * 34);
        bb = my_realloc(bb, sizeof(int) * 14);
        cc = my_realloc(cc, sizeof(float) * 10);

Note that in my_realloc you do not need to cast the datatype as the variable itself is passed and correct my_realloc is called which returns the proper datatype pointer. The my_realloc has overloaded functions for char*, int* and float*.

9.3 Garbage Collector for C++

In C/C++ Garbage Collection is not a standard feature and hence allocating and freeing storage explicitly is difficult, complicated and is error-prone. The Garbage Collection (GC) is not part of the C++ standard because there are just so many ways how one could implement it; there are many GC techniques, and deciding to use a particular one would not be good for certain programs. Computer scientists had designed many GC algorithms, each one of them catering to a particular problem domain. There is no one single generic GC which will tackle all the problem domains. As a consequence, GC is not part of C++ standard, they just left it out. Still, you always have the choice of many freely available C++ libraries that do the job for you.

Visit these sites:

9.4 Anti-crash With const

You must use const at every opportunity. The const is your most powerful anti-crash weapon. An additional benefit is that it makes your code self-documenting. For instance, look at this:


const char *add2strings(const char *sz1, const char *sz2);

Such a declaration guarantee's that no matter what wierd things go on within the function, it can't harm the application programmer's two strings sz1 and sz2. If any memory corruption occurs, it will be to variables within the function's scope. This, of course, greatly reduces side effect bugs.

Furthermore, the declaration of the return pointer as const means that the application programmer can't "reach inside" the function to corrupt its scope. For instance, if the return value is a static array of 40 characters, if the return wasn't static the application programmer could do this:


char *pchName = add2strings("James", "Bond 007");
strcat(pchName, " jumps out of the plane and parachutes down");
cout << "I just corrupted an internal variable of add2strings. ";
cout << "Will I see this message?\n";
cout << "Will it crash later in the program? Who knows!\n";

Fortunately, because add2strings() returns a const pointer, you'll get a compiler error on this:

char *pchName = add2strings("Big galaxies", "Big stars");

Even if you declared pchName as a const char *, the moment you modified its contents with strcat() you'd get a compile error. The const keyword helps the programmer keep any errors localized, thus greatly reducing the likelihood of side-effect errors.

9.5 Forget C

Forget you ever knew printf(). Use cout <<. Of course, scanf() was flirting with disaster even in the rough and ready C days. Use cin >>, or make your own input routines or classes. What about that wonderful sprintf()? Instead of this:


#include <iostream.h>
#include <stdio.h>
/* . . . */
char myaString[100];
sprintf(myaString, "%s, %s %c", myaLname, myaFname, *myaMname);
cout << myaString << '\n';

Try This:
#include <iostream.h>
#include <strstream.h>
/* . . . */
ostrstream ost;
ost << myaLname << ", " << myaFname << " " << *myaMname;
ost.put(0); //null terminate the string cout
cout << ost.str() << '\n';


Next Previous Contents