Be Carefully Consistent With Memory

One of the things that most C/C++ programmers start to take for granted is memory. It’s always there, and when used properly, it always “just works.” However, frameworks throw a bit of a monkey wrench into the equation because they are their own, separate entity. As framework designers, we have to be careful with the way we expose memory to the consumer lest we shatter that illusion that memory “just works.”
The first thing I want to discuss is the take home point I want you to leave with. When designing a framework, pick a convention and consistently stick to it. If you want the framework to allocate and return memory to the consumer, then make sure the framework is also what deallocates that memory. If you want the consumer to always pass in memory to the framework, then make sure the consumer is what frees that memory. If you want to expose reference counted objects (modeled after frameworks like CoreFoundation), that’s fine too. The key here is never to mix and match the conventions. Having the framework create memory that the consumer frees is asking for trouble!

Let’s take a look at why the consistency is critical. There are some obvious things to pick on, such as the framework calling malloc and the consumer calling delete. But let’s assume that your framework has clear documentation and the consumer is following it to the letter.

Let’s say your framework has a function like this:

// This function returns newly allocated memory which you
// should release by calling the CRT's ::free function.
char *CreateStringFromNumber( int i );

Well, maybe. The truth is, you don’t know. As a framework designer, no matter what you do, you cannot guarantee that this situation will work. As a consumer, you can align the stars such that it will work. But most consumers won’t even realize they have to! The problem boils down to one of encapsulation.

When you create a framework, you are making an executable object that stands on its own. So the rules being followed in the framework are its own rules, separate from the application rules of consumers that use the framework. This is where the true issue sneaks in.

If the framework has statically linked in the CRT for some reason (for instance, by using the /MT or /MTd compiler options in VC++), and the application also statically links in its own CRT, then you can have a conflict of implementations. The version of malloc called by the framework may be different from the version of free called by the application. Or, if the framework dynamically links in the CRT but the application statically links it in, you can run into the same situation. The only way for this situation to work is for the CRT to be dynamically linked in to both the framework and the application — but there’s no way as a framework designer for you to enforce this!

A similar situation happens when using operator new and operator delete as the memory scheme. For instance, let’s say that the framework exposes the following function:

// This function returns memory allocated with new[], so
// you must call delete[] when you wish to release it.
char *MakeACharacterArray( void );

and the application abides by the rules like this:

char *str = MakeACharacterArray();
// Do something with str, then delete it according to
// the framework's rules
delete [] str;

You still run into the malloc/free issue described above (after all, the default operators end up using malloc and free anyways). But you also may not realize that custom new and delete functions can come into play as well.

Let’s say the framework has a custom allocator for small chunks of data, but uses the default allocator for larger chunks. Then it may have its own operator new/new[] and operator delete/delete[] implementations. These implementations are not shared with the application! The converse is true as well, and neither the framework nor the application has any way to know about the custom allocators used in the other. As a simple example:

If you create an example project where the framework is a DLL (it requires VC++ because of the __declspec stuff, but you can easily adapt it for gcc, et al) and the application consumes that DLL, you will see the following on the command line:

Helper new[]
Main delete[]

So my recommendation is to always be consistent with your memory allocations and deallocations — this allows you to avoid the sort of issues I’ve raised here. If you allocate it and deallocate it, then you can control how both ends behave. I would not leave that up to chance, as it can cause very subtle bugs and only in certain circumstances. Those are never fun issues to debug!

One Response to Be Carefully Consistent With Memory

In addition, mixing new/free and malloc/delete. Application does not know which way the framework has allocated memory (malloc or new), so as a user of the framework, one should never try to deallocate memory acquired from the framework or any third party library. For example: Some framework is in C and the application is in C++, and you delete/delete[] after acquiring memory from the framework OR some framework is in C++ and the application is in C and you free memory acquired from the framework. It always makes sense to do not mix responsibility, i.e., separation of responsibility:(1.) I allocate, I deallocate (2.) You allocate, you deallocate.

Your email address will not be published. Required fields are marked *

Comment

Name *

Email *

Website

Who

Aaron Ballman is a software engineer for GrammaTech. He has almost two decades of experience writing cross-platform frameworks in C/C++, compiler & language design, and software engineering best practices and is currently a voting member of the C (WG14) and C++ (WG21) standards committees.

In case you can't figure it out easily enough, the views expressed here are my personal views and not the views of my employer, my past employers, my future employers, or some random person on the street. Please yell only at me if you disagree with what you read.