C++ Memory Management: From Fear to Triumphby George Belotsky
- Choosing the Right Language: C++ and Some Alternatives
- Understanding C++ Memory Errors
- Examples of Common C++ Memory Errors
- The Way Forward
- Further Reading
- Additional Example Code and Outputs
Everyone knows that memory management is a difficult and dangerous chore in C++. This series of three articles will show you that the conventional wisdom is not true. When approached correctly, C++'s seemingly archaic memory-management scheme actually provides an opportunity to create spectacular programs — programs that would not be possible with more modern languages that handle memory automatically.
This article, part one in a series, discusses C++ in the context of several other popular languages. It also describes the kinds of memory errors that can occur in C++ programs. The most common specific errors are then presented in a set of tables, for easy reference when developing your own code.
Subsequent articles in the series will demonstrate a different way of thinking about memory management — not as a dreaded danger to be faced with stoic resolve, but as a powerful and subtle tool for improving your work. In many situations, the C++ approach to memory resources is not cause to avoid the language, but actually the reason why it should be used!
Not so long ago, C and C++ were about the only practical choices for serious software development on microcomputers. This is no longer the case. Java, Python, and Perl are three established, mainstream alternatives that can be used for many applications today. These new languages run on multiple platforms, feature extremely rich libraries for everything from encryption to graphics, and are supported by large, enthusiastic user communities. They are freely available (although Java's source code is not open like the other two). All three are widely used in business, government, and research.
In contrast to C and C++, the newer languages (Java, Python, and Perl) have automated memory management. This ranges from Python's simple reference-counted model to Java's sophisticated garbage collector. To the programmer, however, the final result is nearly always the same: no more worrying about memory errors. Why, then, even bother considering C or C++? Why not just choose one of the memory-managed languages and live happily ever after?
In many applications, a memory-managed language is indeed the right choice. Scripts to automate a small to medium-sized web site, for example, are best written in Perl or Python. To analyze your server's logfile, Perl is probably the best choice. A single-user application with an excellent GUI can be built in Java. Complex object-oriented programs are quickly and easily written in Python.
Even older systems written in C or C++ can benefit from the addition of new code in one of the memory-managed languages; Java, Python, and Perl all provide features that make such integration possible. Do C and C++, which require low-level operations to handle memory, have any significant role left to play in modern software development? The answer is yes, as the following discussion will show.
Languages that give you precise control over system resources remain highly relevant to this day — and not only for embedded systems or kernel-level code. If your project is a user-space server or must perform lots of computation (e.g., a graphics package or a game), you will save yourself a great deal of trouble by avoiding the memory-managed languages, at least for the core of your system.
As described previously, many situations are handled by the automated, general-purpose allocation strategies of the memory-managed languages. In many other cases, however, resource management becomes critical. Making efficient use of a server's RAM, for example, is a daunting task with a memory-managed language. Your control is minimal, so precisely matching data structures to algorithms is a highly uncertain process.
It is possible to learn how to use a language such as C++ correctly. On the other hand, trying to get Java's garbage collector (just to pick one example) to take a specific action at a certain time is an impossible task. Sometimes it will work, then break with no explanation. The behavior will depend on which JVM you are using, so your code will not be portable. Even on the same platform, JVM upgrades will unleash mystifying, subtle errors. This is precisely the kind of nightmare that is typically associated with memory allocation errors in C++!
Because memory-managed languages seem so automatic at first glance, many programmers assume that their greatest worries are over once they choose such a language. Hopefully, their luck will hold up, and they can live a happy life. For the multitude that will not be so fortunate, there is a hard and painful lesson waiting here: errors in memory management almost always lead to disaster. A memory-managed language does not change that; it gives you a generic (albeit well-designed) strategy to deal with the problem. If, however, the generic strategy is wrong for your project, then you will have made a memory management error, and you will pay for this error just as surely as if you had coded it yourself.
User-space programs are where C++ really shines. While kernel development, embedded software, and hard real-time systems can also benefit from the use of C++, the simple, predictable C is often the better choice for such applications. In contrast, C++'s richness is often preferable to C's simplicity in user space.
Just like C, C++ will give you precise control over system resources. Also, for a very slight performance penalty over C (a few percentage points at most for a well-written program -- see [Mey98]) C++ offers many more tools for the capable designer. A good architecture for a complex project is much more easily expressed in C++.
C++ is highly suitable for large projects done by small, competent teams. A group of several developers willing to treat C++ with respect will get exceptional results with the language. Using C++ in larger teams can be risky, however. In such situations, it is probably best to build the core of the system in C++, and then augment it with a memory-managed language. Such an arrangement is much more forgiving of programmer error than having several hundred people code only in C++.
If you must use C++ exclusively on a project with many developers, you'll
need to make a small group responsible for all memory allocation. Everyone
else would then rely on the facilities developed by that group (all code should
actually be scanned, making sure that calls to
delete are restricted to the designated part of the program).
Almost all classes developed by a large team should work correctly with C++
default copy constructors and assignment operators (the technical details will
be covered in articles two
of this series). You will also need a system to track the classes that require
custom implementations of these methods, because such classes can easily lead
to very dangerous and subtle memory errors.
While C++'s demands for low-level coding may look clumsy at first glance, elegant C++ programs (such as the widely used Standard Template Library) are amongst the most beautiful of all software. Some truly amazing things are indeed possible with the language; hopefully, this series of articles will help you achieve some of these things yourself.
The modern programmer has a truly breathtaking choice of programming languages. It is beyond the scope of this article — whose main focus is C++ — to discuss them all. In the language overview provided here, several excellent mainstream languages are discussed. Of course, there is a lot more out there: Smalltalk, Fortran, Forth, COBOL, Ruby, Lua, Scheme, Guile, etc. One of these other languages might be the right one for you or your project. So keep exploring, and don't forget that older languages such as C++ are not automatically obsolete even when a great new invention (e.g., Python) comes along.
Having thought about C++ and its alternatives, it is now time to take a specific look at C++ memory management, and see what elegant programs you yourself can build with its powerful, subtle tools.