Preventing dynamic allocation - Embedded.com

Preventing dynamic allocation

Last summer, I wrote a column entitled “Poor reasons for rejecting C++” in which I sought to dispel some misconceptions about C++.1 Among the many reader comments posted online were some valid concerns that merit further discussion. I'll address one of those concerns this month.

Some readers took exception to my statement that, “I know of no place where the C++ language performs dynamic allocation or recursion behind the scenes.” As I explained a few months ago, I think of the C and C++ programming languages as distinct from their accompanying standard libraries.2 Although the various components in the Standard C++ library perform dynamic allocation behind the scenes, the language itself does not.

Nonetheless, you may be legitimately concerned that your C++ code will invoke a function that uses dynamic allocation against your wishes. In that case, you can choose among various techniques that will trigger a compile or link error to alert you that your code is using dynamic allocation.

Probably the simplest such technique is to replace the global operator new with a version that causes a link error. As I explained in an earlier column, a C++ new-expression allocates memory by calling a function named operator new .3 Each C++ environment provides a default implementation for a global operator new , declared as:

void *operator new(std::size_t n) throw (std::bad_alloc); 

However, the C++ Standard lets you define a function with the same name and signature (parameter types) to replace the default implementation.4

To prevent using dynamic allocation, simply define a replacement version of operator new as follows:

// declare this function, but don't define itvoid *operator_new_blocker();void *operator new(std::size_t) throw (std::bad_alloc)    {    return operator_new_blocker();    } 

Hence, a new-expression as in:

int *p = new int; 

will compile, as always, to call operator new . When the linker drags the definition for operator new into the executable, it will also try to drag in operator_new_blocker as well. However, the link will fail because operator_new_blocker isn't defined. Any call to operator new will provoke a link error, whether the call occurs directly in your code or indirectly in library code that your code uses.

Compile errors are generally preferable to link errors because compile errors tend to be more accurate in pinpointing the location and describing the nature of translation errors. However, you can't use compile-time techniques to intercept calls to operator new in previously-compiled components that you're linking into your code. You have to rely on the linker.

You must place the replacement definition for operator new so that the linker will bind the definition into the executable program if and only if at least one call to operator new occurs somewhere in the program. That is, you must ensure that the linker won't incorporate the replacement definition unless the program actually calls operator new . If you inadvertently link operator new into your program, the linker will hunt for a definition for operator_new_blocker that it won't find, and you'll get spurious link errors.

Where you need to place your definition for operator new depends on your development tools. Many modern compilers and linkers employ some form of smart linking that will link into the executable only those external functions and data that the program actually uses. For example, if a single source file contains external definitions for functions f , g , and h , yet the program calls only f and h , a smart linker will link f and h into the program, but omit g . If you're using a compiler with a smart linker, you can place the definition for operator new in any source module that's compiled and linked as part of the build. A program that never calls operator new should still link without complaint.

With compilers and linkers that don't use smart linking, you may have to place operator new into its own separately-compiled source file, and possibly place the resulting object module into a separate library. You must also configure the build so that the linker looks in that separate library before it looks in the standard library.

In truth, defining only one version of operator new won't cover all possible cases. For example, an array-new-expression as in:

char *p = new char [n]; 

allocates memory by calling a function named operator new [] , declared as:

void *operator new [](std::size_t n) throw (std::bad_alloc); 

The Standard C++ library also provides default implementations for “nothrow” versions of operator new and operator new [] , declared as:

void *operator new (std::size_t n, std::nothrow_t const &) throw (); void *operator new [] (std::size_t n, std::nothrow_t const &) throw (); 

By default, these functions return a null pointer, rather than throw an exception, if the allocation fails.

According to the C++ Standard, these four variants of operator new (new, new [] , nothrow new , and nothrow new [] ) are the only replaceable memory allocation functions. If you really want to prevent your C++ programs from using dynamic memory allocation, you should replace all four variants of operator new with versions that cause link errors.

Four variants of operator delete (one corresponding to each replaceable variant of operator new ) are also replaceable. You don't need to replace these to block unintended use of dynamic memory management, but it's probably still a good idea to replace them.

If you want to ensure that your program doesn't use any of the Standard C allocation functions either, you should write corresponding replacements for malloc , calloc , realloc , and free so that calling any one of them triggers a link error as well. Whereas the C++ Standard specifically allows programmers to replace the definitions for certain forms of operator new and operator delete , the C Standard says that replacing any standard library function leads to undefined behavior.5 Nonetheless, I think you'll find writing these replacements for malloc , et. al, will work on nearly all platforms as I've described.

Truth in advertising: Although I have used this technique to successfully prevent using dynamic memory allocation in my own code, I have yet to see it employed in any large scale effort. If you run into difficulties with it, please let me know, and I'll pass it on to my readers.

Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. Dan also welcomes your feedback: e-mail him at . For more information about Dan .

Endnotes:
1. Saks, Dan, “Poor reasons for rejecting C++”, Embedded.com, August 20, 2009. www.embedded.com/columns/219401085.

2. Saks, Dan, “Freestanding vs. hosted implementations”, Embedded.com, October 22, 2009. www.embedded.com/columns/220900159.

3. Saks, Dan, “Allocating objects vs. allocating storage”, Embedded.com, September 2, 2008. www.embedded.com/columns/210200586.

4. ISO/IEC Standard 14882:2003(E), Programming lan5.ISO/IEC Standard 9899:1999, Programming languages-C.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.