C++ 11 Memory Management
Memory management has always been one of the most error-prone areas of C++. The same is true of C. One of the strengths of managed languages, such as Java and C#, is their support for automatic garbage collection. Garbage collection still isn't a feature of C++ 11, so we must still be careful in our memory management. However, there is some good news: C++ 11 has added features that strengthen the memory management area. Before looking into the general problems of memory management, let's take a broad look at the place C++ occupies in the pantheon of mainstream programming languages.
Is C++ a System Language?
What is a system language? To answer this, let's think a little about the precursor to C++. The C language is often referred to as a system language, which broadly means:
- C is a statically typed language, unlike PHP or other scripting languages.
- C allows the definition of arbitrarily complex data structures.
- C is a compiled language.
- C code is meant to operate independently of other programs.
While these rules might be disputed, it seems to me that if we accept them as the basic precepts for a system language, then C++ is also a system language. Typically, system languages are used to build such exotic artifacts as operating systems and embedded systems. In general, system languages are not used to develop application software. Instead, applications are typically written using general-purpose languages such as Java or C#.
Interestingly, it used to be the case that embedded systems were exclusively built using languages such as C (or with proprietary languages), but the use of C++ is increasingly an active option in this rarefied domain. Another example is the case of Apple iPhone apps, which are built using the proprietary language Objective C. I've even read somewhere that Microsoft Windows is supposed to be the biggest C++ application in the world! So, it would definitely appear that C++ is now a fully fledged system language.
What else can we say about system languages?
Given that system languages are used for very specialized work, it's fair to say that such languages are difficult to master. I think that C++ is no exception, and the area of memory management is a good example of the skill required to produce successful results.
Indeed, one of the main problem areas in both C and C++ is the need for very disciplined memory-management code. How often have you had a case where a large C/C++ program operates successfully on short runs, whereas on long runs the code exhausts available memory and crashes unceremoniously? Or worse still, it just crashes without warning?
Happily, C++ 11 provides some very useful tools to tackle this problem, which should help the language to compete a little better with the likes of the so-called managed languages Java and C#.
Java and Memory Leaks
It's perhaps not so well known that, just like C/C++, Java programs can leak memory. Imagine a Java method that instantiates an array of large objects. This array of objects will continue to reside in memory until no references remain to it[md]in fact, only when no references survive does the allocated memory become a candidate for garbage collection.
While this behavior is objectively correct, it may not be what the programmer intended, particularly since Java is often touted as being a language that takes care of such arcane matters as memory management. The plain truth is that sufficiently badly written Java code can leak memory like a sieve, and the same is true of C#.
Let's look at some typical C++ memory-leakage scenarios.
The Problem of C Memory Leaks
It's surprisingly easy to produce a C memory leak, as illustrated in Listing 1.
Listing 1—A memory leak in C.
char* a = (char*)malloc(256); char* b = (char*)malloc(256); a = b; // Memory leak!
In Listing 1, the original memory pointed to by variable a is gone; that is, it can no longer be accessed by the program. In other words, the 256-byte block has leaked[md]it's as simple as that. Listing 1 illustrates just how easy it is to leak memory. Repeated often enough, the simple code in Listing 1 could potentially exhaust the memory on the largest of systems.
In Listing 2, we avoid the memory leak by allocating a new intermediate pointer to store the original block to which variable a pointed.
Listing 2—Trying to avoid the memory leak from Listing 1.
char* a = (char*)malloc(256); char* b = (char*)malloc(256); char* c = (char*)malloc(256); memcpy (c, a, 256); // then a = b;
That's a couple of examples in C. Now what about C++ memory leakage? Listing 3 shows a C++ memory leak.
Listing 3—A memory leak in C++.
ABigClass* a = new ABigClass(); ABigClass* b = new ABigClass(); a = b; // Memory leak!
Just as we saw in Listing 1, the code in Listing 3 again "loses" the memory to which object a originally pointed. Given that C++ still has no automatic garbage collection, the lost memory won't be deallocated until the program exits; therefore, we've leaked the memory.
Having seen some memory-leakage pitfalls, let's consider some guidelines for handling these and similar issues.