C++ Smart Pointers with Aligned Malloc/Free

Recall in the aligned_malloc article that we noted the need to pair aligned_malloc with aligned_free. Using the wrong free call can cause serious problems, as we have modified the pointer that malloc originally returned to us.

This problem of pairing allocators and deleters also applies in other situations: new must be paired with delete, while new[] must be paired with delete[].

But what if I free the memory at the later stage and don't realize it's an aligned pointer (or was allocated with new[])? What if I just have a brain fart? How can we protect against using the incorrect free or delete call?

In my earlier article about C++11 smart pointers, I highlighted that we can specify a deleter when declaring our smart pointers. When the pointer goes out of scope or is reset, the correct deleter is automatically called. Let's use this to protect ourselves from introducing unnecessary errors.

std::unique_ptr

If you are using a deleter with std::unique_ptr, you must specify the prototype for your deleter function in the pointer type.

std::unique_ptr<uint8_t, decltype(&aligned_free)>

This can be quite tedious to type, so instead I recommend creating an alias. We can define a unique_ptr_aligned type that includes our aligned_free prototype while leaving the type as templated value:

template<class T> using unique_ptr_aligned = std::unique_ptr<T, 
    decltype(&aligned_free)>;

The primary benefit from the alias is that you can use it in multiple locations and function prototypes without the pesky decltype(&aligned_free) being typed everywhere.

Now that we have our alias, we can use it to create a new aligned pointer. Note that you need to use aligned_malloc rather than new to generate the memory to pass into the constructor. We also must still specify the specific deleter function we need to call.

unique_ptr_aligned<uint8_t[]> p(
    static_cast<uint8_t*>(aligned_malloc(32, 1024)), 
    &aligned_free);

That's still quite a bit of typing. Wouldn't it be better if I could just type the following?

auto p = aligned_uptr<uint8_t>(32, 100);

Luckily, with templates, we can:

template<class T>
unique_ptr_aligned<T> aligned_uptr(size_t align, size_t size)
{
    return unique_ptr_aligned<T>(
        static_cast<T*>(aligned_malloc(align, size)), 
        &aligned_free);
}

We have just hidden the details within our templated function. We can make sure all aligned_uptr calls pass through aligned_malloc and specify aligned_free as the detail, leaving us to simply worry about the type, the alignment, and the memory allocation size.

std::shared_ptr

std::shared_ptr is an easier case to handle than std::unique_ptr. While std::unique_ptr requires the deleter to be part of the pointer type, std::shared_ptr does not. You simply need to include the deleter in the constructor call for your std::shared_ptr:

std::shared_ptr<uint8_t> p(
     static_cast<uint8_t*>(aligned_malloc(32, 128)), 
     &aligned_free);

Like with the std::unique_ptr example, we can shorten this typing significantly with a templated function:

template<class T>
std::shared_ptr<T> aligned_sptr(size_t align, size_t size)
{
    return std::shared_ptr<T>(
        static_cast<T*>(aligned_malloc(align, size)), 
        &aligned_free);
}

Resulting in this much simpler declaration:

auto p = aligned_sptr<uint8_t>(64, 100);

Putting it all together

I have uploaded a "smart pointer aligned" example to the embedded-resources github repo. To build the example in examples/cpp, simply run:

make aligned_ptr

I also added memory.h to examples/c, where the aligned_malloc and aligned_free prototypes live.

The smart pointer example uses malloc_aligned.c, so I have moved the COMPILE_AS_EXAMPLE definition to the C examples Makefile. This change allows the C++ examples to use malloc_aligned.c as a library by removing the main function.

Further Reading