C++ Casting, or: “Oh No, They Broke Malloc!”

The first time you try compiling your C code with a C++ compiler can be terrifying. One of the most troublesome offenders is malloc, and you will have your eyes opened to the number of implicit type conversions in your code.

Table of Contents:

  1. malloc: C vs C++
  2. C++ Casts
    1. static_cast
    2. reinterpret_cast
    3. const_cast
      1. const_cast and volatile
    4. dynamic_cast
  3. C-style Casts
  4. Casting Recommendations
  5. Updating Our malloc Calls
  6. Further Reading

malloc: C vs C++

Consider the following:

int * p = malloc(10);

Recall that malloc returns a void *. The C compiler will happily convert the void * result into whatever type you require. Lines of code like this are likely scattered throughout your C projects.

The C++ compiler is not as kind. Unlike C, the C++ compiler allows implicit conversions TO a void * type, but to convert FROM a void * type requires an explicit cast.

error: cannot initialize a variable of type 'int *' with an rvalue
      of type 'void *'
        int * p = malloc(10);
              ^   ~~~~~~~~~~
1 error generated.

So what can we do about this?

The first thing that comes to your mind is our friend, the C-style cast:

int * p = (int*)malloc(10);

This will work, but this style of cast is not recommended in C++. There are more explicit methods which allow us to describe the intention of our cast.

C++ Casts

C++ provides a variety of ways to cast between types:

  • static_cast
  • reinterpret_cast
  • const_cast
  • dynamic_cast
  • C-style casts

Each of the C++ casts has the following generic form:

cast_name<cast_to_type>(item_to_cast)

Let’s look at what each of these casts do.

static_cast

static_cast is the main workhorse in our C++ casting world. static_cast handles implicit conversions between types (e.g. integral type conversion, any pointer type to void*). static_cast can also call explicit conversion functions.

int * y = static_cast<int*>(malloc(10));

We will primarily use it for converting in places where implicit conversions fail, such as malloc.

There are no runtime checks performed for static_cast conversions.

static_cast cannot cast away const or volatile.

reinterpret_cast

reinterpret_cast is a compiler directive which tells the compiler to treat the current type as a new type.

You can use reinterpret_cast to cast any pointer or integral type to any other pointer or integral type. This can lead to dangerous situations: nothing will stop you from converting an int to a std::string *.

You will use reinterpret_cast in your embedded systems. A common scenario where reinterpret_cast applies is converting between uintptr_t and an actual pointer or between:

error: static_cast from 'int *' to 'uintptr_t'
      (aka 'unsigned long') is not allowed
        uintptr_t ptr = static_cast<uintptr_t>(p);
                        ^~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Instead, use this:

uintptr_t ptr = reinterpret_cast<uintptr_t>(p);

reinterpret_cast cannot cast away const or volatile.

const_cast

const_cast adds or removes const from a variable. Strangely enough, you can also use const_cast to add or remove volatile from a variable. No other C++ cast can add or remove these keywords.

Use this cast carefully. If you declared the original variable as const, it’s not safe to remove const and start modifying the underlying data.

You should primarily use const_cast to add const to a variable (such as for a function overload to use a const version). If you need to remove const from a variable, I recommend stopping and thinking about why you are in this situation.

const_cast and volatile

Removing volatile from a keyword is definitely more common in embedded systems than removing const. If you decide to use the volatile variable as a function parameter, you will need to remove the keyword.

Consider the following contrived example:

void test(int * x)
{
  //do something
}

int main(void)
{
  volatile int p = 0;
  test(&p);

  return 0;
}

In C, this example would compile. C++ throws an error:

test.c:14:2: error: no matching function for call to 'test'
        test(&p);
        ^~~~
test.c:5:6: note: candidate function not viable: 1st argument ('volatile int *')
      would lose volatile qualifier
void test(int * x)
     ^
1 error generated.

To prevent this error, we will need to use const_cast:

test(const_cast<int*>(&p));

dynamic_cast

I have never used dynamic_cast in my embedded projects, and I usually keep run-time-type-information (RTTI) disabled. I will provide a cursory overview, but please see “Further Reading” below for more detailed information.

We use dynamic_cast to handle polymorphism. dynamic_cast can convert pointers and references to any polymorphic type at run-time, primarily to cast down a type’s inheritance hierarchy. If dynamic_cast can’t find the desired type in the inheritance hierarchy, it will return nullptr for pointers or throw a std::bad_cast exception for references.

C-style Casts

A C-style cast in C++ tries the following casts in order, using the first C++ cast that works:

  1. const_cast
  2. static_cast
  3. static_cast, then const_cast (change type + remove const)
  4. reinterpret_cast
  5. reinterpret_cast, then const_cast (change type + remove const)

Note that dynamic_cast is never considered when using a C-style cast.

Casting Recommendations

Five casts is a lot to keep in mind. Here are some quick rules of thumb for these new casts:

  1. Use static_cast for your ordinary conversions
  2. Use reinterpret_cast for specific cases were you need to reinterpret underlying data (e.g. converting from a pointer to uintptr_t)
  3. Use dynamic_cast for converting pointers and references along an inheritance hierarchy
  4. Only use dynamic_cast on classes with virtual members
  5. Use const_cast when you need to remove const or volatile keywords. Think carefully before using this cast.
  6. Avoid C-style casts. Be explicit in your intentions.

One unappreciated benefit of using C++-style casts is you can search through your code and immediately locate all casts. With C-style casts, you cannot easily tell the difference between a declaration and a cast if you are performing a search.

Updating Our malloc Calls

That was quite the long detour!

Coming back to our malloc example, we can now see that we should use a static_cast:

int * p = static_cast<int*>(malloc(10));

All that casting really is a nuisance, isn’t it?

Further Reading

If you find C++ casting still to be unclear, try the following two links:

These are useful references if you need to refer to API documentation:

Using C++ Without the Heap

Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.

Learn More on the Course Page

Migrating from C to C++ Articles

3 Replies to “C++ Casting, or: “Oh No, They Broke Malloc!””

    1. Hi Artsiom,

      Agreed that for new code you should definitely use new. This article is written from the perspective of migrating from C to C++ and explaining why existing C code calling malloc() will no longer compile in C++.

  1. Please note that using malloc on anything other than primitive types (eg stuff with a constructor) and proceeding to use them is UB, one will need to use the placement new first to start the object’s lifetime.

Share Your Thoughts

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