We’ve been looking at using C++ to manipulate I/O hardware. Previously, we’ve looked at the fundamentals of hardware manipulation; and how to encapsulate these mechanisms into classes. If you’ve not been following along I’d recommend reading the previous articles first before continuing.

This time we’ll explore a lesser-known feature of C++ and its application in hardware manipulation – placement new.

Before we start

We (should) all know that, apart from the default access specifiers, classes and structs are pretty-much identical; anything that you can do with a class you can do with a struct.

However, to maintain code clarity I’m going to exclusively use structs for Plain Old Data (POD) types and classes for behavioural types.

Structure overlay

If you’re accessing a hardware I/O device with multiple registers it’s very convenient (and efficient) to do so with a structure overlay. A structure is defined, (exactly) matching the layout of the hardware registers and this is ‘overlayed’ onto I/O memory using a pointer. Remember, as with all hardware manipulation, we need to make our pointer (this time to a struct) volatile.

struct UART_Registers { std::uint8_t tx; std::uint8_t ctrl; std::uint8_t rx; std::uint8_t status; }; // Confirm that there is no // padding in the struct. // static_assert(sizeof(UART_Registers) == sizeof(std::uint8_t) * 4)); // Define the overlay pointer // auto const uart_ptr { reinterpret_cast<volatile UART_Registers*>(0x40021000) };

For the purposes of this exercise I’m going to assume 8-bit registers, contiguous in memory. This saves us from having to worry about padding or packing issues (which, although valid, will just add unnecessary complication to this article).

Of course, with a structure clients are free to manipulate any of the registers, in ways that might not be appropriate or safe.

int main() { uart_ptr->ctrl = 0b00000001; // Is this valid on our system? ... uint8_t data { }; uart_ptr->rx = data; // What does this do?! }

Class overlay

Uncontrolled access to underlying hardware is unlikely to be a robust solution. Using a class allows us to provide encapsulated access to the hardware via member functions (the actual implementation of our UART is unimportant for this article).

class UART { public: UART(); ~UART(); std::uint8_t read() const; void write(std::uint8_t data); private: std::uint8_t tx; std::uint8_t ctrl; std::uint8_t rx; std::uint8_t status; }; UART::UART() { // Initialise and configure // hardware for use... } std::uint8_t UART::read() const { // Wait for data... // while((status & (1 << 3)) == 0) { } return rx; }

We can once again perform structure overlay. Now, we have protection for our registers and an abstract functional interface to program to.

int main() { auto const uart { reinterpret_cast<volatile UART*>(0x40021000) }; uart->ctrl = 0b00000001; // FAIL! => ctrl is private ... auto data = uart->read(); // FAIL! Eh? }

It should be no surprise that the first use of uart_ptr fails – we are attempting to access a private member of the UART class.

The second failure is more perplexing. The read() function is marked as public, so this isn’t the issue. The error message is no less helpful:

No known conversion for implicit 'this' parameter from 'volatile UART*' to 'UART*'

cv-qualifers means const AND volatile

If you’re familiar with const-correctness in your code this error should look familiar (If you’re not familiar with const-correctness, here’s a good overview.

When we build const-correct classes we mark member functions as const if they only inspect the owning object; not modify it. Technically, marking a member function as const changes the type of the ‘this’ pointer to a const-pointer-to-const-type.

The thing is, this also holds true for volatile objects (that’s why they’re known as the cv-qualifiers and not the c-qualifer and v-qualifer!)

When we did our structure (class) overlay we declared the the pointer as a volatile UART*. This is perfectly correct – since we are overlaying on hardware we don’t want the compiler to optimise away any read/write operations (Have a look here [LINK] for a more complete discussion of the use of volatile).

Just as you cannot call a non-const member function on a const object, you cannot call a non-volatile member function on a volatile object!

Bizarre but true!

To fix this we would have to modify our UART class.

class UART { public: UART(); ~UART(); std::uint8_t read() const volatile; void write(std::uint8_t data) volatile; private: std::uint8_t tx; std::uint8_t ctrl; std::uint8_t rx; std::uint8_t status; }; UART::UART() { // Initialise and configure // hardware for use... } uint8_t UART::read() const volatile { // Wait for data... // while((status & (1 << 3)) == 0) { } return rx; }

Note the read() function has to be marked as both const and volatile.

Now we get the behaviour we were expecting.

int main() { auto const uart { reinterpret_cast<volatile UART*>(0x40021000) }; uart->ctrl = 0b00000001; // FAIL! As expected ... auto data = uart->read(); // OK }

When we declare an object as volatile (either in its declaration or via a pointer) we are saying that all attributes of the class must be treated as volatile objects. Perhaps a more intuitive way to specify this is to explicitly volatile-qualify our class members instead.

class UART { public: UART(); ~UART(); std::uint8_t read() const; void write(std::uint8_t data); private: volatile std::uint8_t tx; volatile std::uint8_t ctrl; volatile std::uint8_t rx; volatile std::uint8_t status; }; int main() { auto const uart { reinterpret_cast<UART*>(0x40021000) }; uart->ctrl = 0b00000001; // FAIL! As expected ... auto data = uart->read(); // OK }

Placement new

Our code now compiles (Quick – ship it!) but it probably still won’t work properly. The reason? The underlying hardware is not being initialised.

The initialisation code for our UART is (quite reasonably) in its constructor code. However, the constructor for the UART class is never being called!

Let’s review this line of code:

auto const uart { reinterpret_cast<UART*>(0x40021000) };

This code effectively says “treat the address 0x40021000 as if there was a UART object at that location”. At no point is that UART object ever constructed (and therefore never initialised).

Enter placement new.

The default operator new allocates memory for an object, then calls its constructor. The return value is the address of the new object.

Placement new is an overload of operator new that does not allocate memory, but just constructs an object at a – provided – memory location. The return value from placement new is simply the supplied address.

The signature of placement new is:

void* operator new(std::size_t, void*);

Note the placement new takes a second parameter – the address to construct the object at. You can use placement new to construct your objects at any location in memory you choose; for example:

class ADT { // ... }; static uint8_t pool [64]; int main() { // Allocate the ADT object in our static // array, pool // ADT* const adt { new(reinterpret_cast<void*>(pool)) ADT { } }; ... }

(Note: To be more correct, I should have used static_cast in the above code. I’ve stuck with reinterpret_cast to avoid confusion with the rest of the article)

We can make use of placement new to perform structure overlay on our hardware; and guarantee the constructor is called.

int main() { auto const uart { new (reinterpret_cast<void*>(0x40021000)) UART { } }; auto data = uart->read(); ... }

Notice we can’t use constexpr here, since the UART is (as far as the language is concerned) a dynamic, not a compile-time, object; and we can’t use reinterpret_cast<> in constant-expressions (and, in case you were wondering, static_cast<> won’t help either).

What about placement delete?

If we are using new, the usual idiom is to pair it with a call to delete. However, there is no placement delete in C++ (you can read a discussion on the topic here).

Don’t be tempted to call operator delete on your placement new’d object, either. The behaviour of delete is to call the destructor on the object, then free the memory associated with the (provided) pointer. In our case, that memory is somewhere in the I/O space! The chances of that ending well are slim-to-none!

If you want to ensure your class’s destructor is called you must call it explicitly:

int main() { auto const uart { new (reinterpret_cast<void*>(0x40021000)) UART { } }; auto data = uart->read(); ... uart->~UART(); // Explicitly call destructor for cleanup. }

Other limitations of class overlay

This is all looking very neat but there are a couple of important limitations on using classes for hardware memory overlay:

You can’t have virtual functions on the class

The class can’t have any additional attributes, beyond the registers

Think about where our overlay object is being constructed: I/O memory. This is not normal memory. It does not consist of consecutive bytes of data memory. Attempting to store additional attributes will cause them to be read from / written to undefined memory:

Possibly other hardware registers, where they could randomly change, according to the whims of the underlying hardware device;

They could corrupt the operation of hardware devices

Writing to undefined memory locations could (silently)fail or produce junk values

Putting attributes in the wrong place in the class declaration could ‘slew’ the hardware overlay layout

Virtual functions require the use of a virtual table pointer as part of the object. This pointer is implicitly added to the object by the compiler; normally at the object’s base address. This pointer would (probably) be overlaid on a hardware register (which might be changing!) or in undefined memory. The results of this (to quote the MISRA-C++ guidelines) “may not meet developer expectations”!

Summary

Placement new is a useful idiom to know for hardware manipulation. Although it can work there are a number of elements that must be remembered:

All class members must be volatile

You cannot have virtual functions on the class

The class cannot have any additional attributes

You must never explicitly delete an object allocated with placement new

The class destructor is not implicitly called; you must call it explicitly yourself

In the next article of this series we’ll have a look at another approach to bit manipulation using bitfield structures and unions.