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:
malloc
: C vs C++- C++ Casts
- C-style Casts
- Casting Recommendations
- Updating Our
malloc
Calls - 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:
const_cast
static_cast
static_cast
, thenconst_cast
(change type + remove const)reinterpret_cast
reinterpret_cast
, thenconst_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:
- Use
static_cast
for your ordinary conversions - Use
reinterpret_cast
for specific cases were you need to reinterpret underlying data (e.g. converting from a pointer touintptr_t
) - Use
dynamic_cast
for converting pointers and references along an inheritance hierarchy - Only use
dynamic_cast
on classes with virtual members - Use
const_cast
when you need to removeconst
orvolatile
keywords. Think carefully before using this cast. - 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:
- operator
new
- cppreference: explicit casts
static_cast
conversionreinterpret_cast
conversionconst_cast
conversiondynamic_cast
conversion
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
Why not just use new instead of malloc() ?
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++.
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.