Articles & Books

C++ for Embedded Systems: constexpr and consteval -- Andreas Fertig

me.pngIn today's post, I'll learn how modern C++ can influence the code you write for your embedded system. You will see code using up to C++23. The example I show you below circles around at least two questions I got various times from customers: What is consteval good for? What is that user-defined literal operator, and why should I care?

C++ for Embedded Systems: constexpr and consteval

by Andreas Fertig

From the article:

Chapter One: What is a MAC address?

I teach a lot of classes to customers who are developing embedded systems. That makes sense. I worked for a long time in that domain, and I enjoyed it so much.

One recurring topic is networking. While nowadays we have various different network types and technologies, let's use the Internet Protocol (IP) today. The base of network communication is your Network Interface Card (NIC). Each NIC has a unique Medium Control Address (MAC) assigned. The MAC address is the base layer for everything on top, like TCP/IP.

A MAC address consists of exactly six bytes. One way to represent a MAC address in code is this:

embedded-fertig.png

A Simplified Overview of Ways to Add or Update Elements in a std::map -- Raymond Chen

RaymondChen_5in-150x150.jpgThe std::map subscript operator ([]) is a convenient but sometimes dangerous feature, as it can create unintended default-constructed entries. By understanding the behavior of various map insertion and lookup methods—such as insert, emplace, try_emplace, and insert_or_assign—developers can write more efficient and predictable code while avoiding unnecessary key-value creations and duplicate lookups.

A Simplified Overview of Ways to Add or Update Elements in a std::map

by Raymond Chen

From the article:

Some time ago, I mentioned how the std::map subscript operator is a dangerous convenience. In that article, I linked to an overview of the insertion emplacement methods, but I’m going to recapture the essential points in a table.¹

In the table below, the discussion of “consumed” or “not consumed” refers to the case that v is an rvalue reference like std::move(something).

stdmap-chen.png

We can reorganize the table by effect.
stdmap2-chen.png
Exercise: Why are the bottom left two boxes blank?

C++26: pack indexing -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGC++26 introduces pack indexing as a core language feature, making it significantly easier to extract specific elements from parameter packs using a familiar subscript syntax. This improvement, proposed by Corentin Jabot and Pablo Halpern, eliminates the need for cumbersome workarounds like recursive templates or boolean expression tricks, providing a more intuitive and readable approach.

C++26: pack indexing

by Sandor Dargo

From the article:

C++11 introduced parameter packs to provide a safer way to pass an undefined number of parameters to functions instead of relying on variadic functions.

While packs are a useful feature, and since C++17 it’s so easy to use them in fold expressions, extracting a specific element of a pack is somewhat cumbersome.

You either have to rely on some standard functions not made for the purpose or use “awkward boolean expression crafting or recursive templates”. None of them is unbearable, but it might be error-prone or simply expensive regarding compile-time performance. Nevertheless, they are not the most readable solutions.

C++26 brings us pack indexing as a core language feature thanks to the proposal of Corentin Jabot and Pablo Halpern, P2662R3.

Before discussing some interesting points, first, let’s look at an example:

packindexing-dargo.png

Improving Code Safety in C++26: Managers and Dangling References -- Bartlomiej Filipek

codesafety2-filipek.pngIn this blog post, we’ll explore ways to improve the safety of a simple configuration manager. We’ll handle common pitfalls like dangling references and excessive stack usage. Additionally, we’ll see how C++26 helps enforce safer coding practices with stricter diagnostics and improved handling of large objects.

Improving Code Safety in C++26: Managers and Dangling References

by Bartlomiej Filipek

From the article:

Step 1: The Buggy Implementation 
 
Below is a simple example of a manager object that stores various configs in a map and provides a method to retrieve them. When a requested configuration isn’t found, the code attempts to return a default certificate:

codesafety-filipek.png

Do you see a potential error in this code?

. . .

At first glance, the code looks harmless. However, if the requested entry isn’t found, the function returns a reference to a temporary std::vector. Once the function exits, that temporary is destroyed—leaving you with a dangling reference and undefined behavior.

Inside STL: Waiting for a std::atomic to change, part 2 -- Raymond Chen

RaymondChen_5in-150x150.jpgLast time, we looked at how the Microsoft C++ standard library implements wait and notify_* for std::atomic<std::shared_ptr<T>>. Today, we’ll look at the other library that (as of this writing) implements std::atomic<std::shared_ptr<T>>: libstdc++.

Inside STL: Waiting for a std::atomic<std::shared_ptr<T>> to change, part 2

by Raymond Chen

From the article:

The first thing to note is that the traditional “wait for a value to change” mechanism on unix is the futex, but futexes (futexen?) are limited to 4-byte values, which is insufficient for a 64-bit pointer, much less the two pointers inside a shared_ptr.

At this point, I will refer you to learn about how libstdc++ implements waits on atomic values, particularly the section on how it handles types that do not fit in a __platform_wait_t. The remainder of this discussion will treat that as an already-solved problem and focus on the shared pointer part.

Okay, back to atomic<shared_ptr<T>>::wait():

// atomic<shared_ptr<T>>::wait
void
wait(value_type __old,
     memory_order __o = memory_order_seq_cst) const noexcept
{
    _M_impl.wait(std::move(__old), __o);
}

When you wait on a shared_ptr, the work is done by _Sp_atomic::wait:

// _Sp_atomic<shared_ptr<T>>::wait
void
wait(value_type __old, memory_order __o) const noexcept
{
    auto __pi = _M_refcount.lock(memory_order_acquire);
    if (_M_ptr == __old._M_ptr && __pi == __old._M_refcount._M_pi)
      _M_refcount._M_wait_unlock(__o);
    else
      _M_refcount.unlock(memory_order_relaxed);
}

Making C++ Safer -- Greg Law

Should you start new projects in C++, these days? Is language safety an issue for you? What can be done, today?

Making C++ Safer

by Greg Law

From the article:

I believe that over time C++ will become a lot safer, maybe even some kind of ‘safe’. Competition is good: Clang was the best thing to happen to GCC, and Rust might turn out to be the best thing to happen to C++. That journey has already begun, with proposals for the evolution of the language including Contracts and Profiles, and simply changing some of the defaults in C++26. While the language custodians work to make the language itself safer, what can you do today?

 

Inside STL: Waiting for a std::atomic to change, part 1 -- Raymond Chen

RaymondChen_5in-150x150.jpgWhen using std::atomic<std::shared_ptr<T>>, the C++ standard defines a "change" as a modification to either the stored pointer or the control block pointer. However, since atomic wait mechanisms typically track only a single memory address, the Microsoft implementation handles this limitation by using a timeout-based polling strategy to detect changes in the control block.

Inside STL: Waiting for a std::atomic<std::shared_ptr<T>> to change, part 1

by Raymond Chen

From the article:

Like other std::atomic specializations, std::atomic<std::shared_ptr<T>> supports the wait and notify_* methods for waiting for the value to change and reporting that the value has changed. The definition of “changed” in the C++ language specification is that the value has changed if either the stored pointer or the control block pointer has changed. A shared pointer is implemented as a pair of pointers, but Wait­On­Address can wait on at most 8 bytes, and unix futexes can wait on only four bytes, so how does this work?¹

The Microsoft implementation waits for the stored pointer to change, and the notify_* methods signal the stored pointer. But wait, this fails to detect the case where the stored pointer stays the same and only the control block changes.

std::atomic<std::shared_ptr<int>> p =
    std::make_shared<int>(42);

void change_control_block()
{
    auto old = p.load();
    auto empty = std::shared_ptr<int>();

    // Replace with an indulgent shared pointer
    // with the same stored pointer.
    p.store({ empty, old.get() });
    p.notify_all();
}

void wait_for_change()
{
    auto old = p.load();
    p.wait(old);
}

C++26: A Placeholder with No Name -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGIn this post, we are going to discuss a core language feature proposed by Corentin Jabot and Micheal Park in P2169R4. With the new standard we get a cool unnamed placeholder.

C++26: A Placeholder with No Name

by Sandor Dargo

From the article:

By convention, when we have a variable whose value we don’t want to use or care about, we often name it _. The problem is that with higher warning levels (-Wunused-variable), our compilation might fail because _ is unused.
int foo() {
return 42;
}

auto _ = foo();
/* error: unused variable '_' [-Werror,-Wunused-variable] */

To avoid this problem, we must mark it [[maybe_unused]].

 

Write More C++ Code Thanks to constexpr -- Andreas Fertig

me.pngSince its introduction, the constexpr keyword in C++ has steadily evolved with each new standard, becoming an increasingly powerful tool for compile-time computation and optimization. In this article, I’ll share a real-world example of how constexpr helped optimize memory usage and improve performance for an embedded system project, showcasing its potential to transform how we approach C++ programming.

Write More C++ Code Thanks to constexpr

by Andreas Fertig

From the article:

Since the keyword constexpr and its behavior got included in C++, it has been improved in each and every new standard of the language.

I'm a big fan of constexpr and am not alone. Jason Turner is also very vocal, having coined the term "constexpr all the things".

Well, demonstrating the powers of constexpr is nonetheless something difficult. I know that from my training classes and various consulting contracts. Today, I'd like to share a story from back in time when a customer hired me to consult. They did develop an embedded system and ran out of memory. Not during run-time, but before. The features they wanted to put in the chip were too big in code size and somewhat RAM.

Initial constexpr-free example

They used a class I've seen a couple of times in embedded systems with some variations. A string brings its memory picky-back. 

 

 

Reverse Iterations -- Coral Kashri

c-senioreas-v9.pngSometimes, we all need a way to iterate over a container in the opposite direction. There are several ways to reverse-iterate a container, and in this article, we’ll explore them.

Reverse Iterations

by Coral Kashri

From the article:

Probably the simplest way, taken from C is to iterate using an index location:
for (int64_t index = ssize(container); index >= 0; --index) {
    // do something with `container[index]`
}
This way is highly not recommended as it might lead to infinite loops if done incorrectly (for example by using uint64_t or size_t for the index type), and you can find more issues with this way in some previous articles about iterators in this blog.