Return to SwATips | pdf |
Resource Acquisition Is Initialization (RAII). When working with legacy code, often variables and memory management do not use RAII concepts. When memory is created in an uninitialized state, developers risk the use of uninitialized memory further down in the application data flow.
To solve this, C++ implemented two ways of initializing new memory: default initialization and value initialization. While default initialization is typically faster, developers quickly realized that there was rarely a reason to keep memory uninitialized. Initializing memory as soon as it’s allocated keeps developers from shooting themselves in the foot.
One of the best ways to prevent memory errors is to use RAII. Particularly in exception-based code, RAII concepts provide a memory-safe technique for resource management. Several issues face developers when maintaining legacy code with new programming concepts, particularly in the DoD. Legacy code is generally void of RAII concepts and has not been built with the latest standards of safe programming practices.
The first option for maintaining legacy code in a memory-safe way is to update the code. Wrapping old pointers in smart pointers can help make the code more maintainable.
There are several issues with updating the old code to the RAII-compliant code:
Developers can introduce double free conditions where a pointer is managed by both a smart pointer and in the code itself. Also, conditions arise when a single pointer is handed over to multiple smart pointers for management.
At the time of this writing, the latest versions of Fortify, Coverity, Checkmarx, Parasoft, clang-analyzer, and the GCC 10 -fanalyzer flag are all incapable of identifying the double free in Listings 3 and 4.
We’ve also seen a large number of initialization errors when updating to smart pointers. Using the above examples, it’s tempting for a developer to use shortcuts and commit the error demonstrated in Listing 5.
The unpredictable nature of this issue can be demonstrated in the example in Listing 6 where the Boolean uses default initialization and obtains a random value from previously-freed memory.
Consider updated code that uses a multitude of different memory handling methods. Some memory is managed manually using malloc and free. Some pass the previous pointer values to smart pointers for their management. Others have been updated to use the make_shared construct. Still others have been updated to custom classes and structs.
Consistency is more important than updating. Introducing multiple memory handling patterns to a section of code increases its complexity. If old memory handling methods cannot be replaced or are not planned to be replaced, it may make more sense to stick with confusing (but consistent) code rather than adding additional complexity.
Finally, some special considerations are needed for allowing smart pointers to handle arrays. The shared_ptr construct permits the developer to define a custom deleter like the one for this array of 10 Booleans:
Smart pointers and RAII concepts help developers prevent memory errors that can plague software. Updating legacy code to RAII concepts can increase its maintainability, usefulness, and security. Nevertheless, updates to the code should not come at a price of inconsistency. When updates can only be applied partially or introduce additional complexity, consistency should be preferred.
Jon Hood, ed. SwATips. https://www.SwATips.com/.
Bjarne Stroustrup. “Exception Safety: Concepts and Techniques”. In: (2001). url: https://www.stroustrup.com/except.pdf.