constexpr is a keyword in C++ that allows the compiler to evaluate expressions at compile time. This is a powerful feature that can significantly optimize performance by reducing runtime overhead.

However, I mainly use it for type-related operations. I seldom apply it to data-related tasks, since defining data with constexpr requires constant values, which is rarely feasible in my projects.

Code with constexpr

#include <stddef.h>
#include <string_view>
#include <algorithm>
#include <cstdio>

template<size_t N>
class FixedString {
    size_t mSize{};
    char mData[N]{};

public:
    FixedString() = default;

    // Constructor that computes string length at compile time
    constexpr FixedString(const char* str) : mSize{std::char_traits<char>::length(str)} {
        std::copy_n(str, size(), mData);
    }

    constexpr size_t size() const { return mSize; }
    constexpr std::string_view string_view() const { return {mData, mSize}; }
};

template<size_t N>
constexpr auto  make_fixed_string(const char (&str)[N]) {
    return FixedString<N>{str};
}

constexpr const static FixedString<50> x{"Hello, embedded World!"};
constexpr const static auto y = make_fixed_string("Hello, some other planet!");


int main () {
    printf("x: %s, %lu\n", x.string_view().data(), sizeof(x));
    printf("y: %s, %lu\n", y.string_view().data(), sizeof(y));
}

Assembly Code with constexpr

With constexpr, the compiler will optimize the code at compile time, resulting in the 20-line assembly code.

Notably, the code uses .rodata exclusively (read-only data section) for accessing string literals and constant values. The data in this section does not change during execution, and it’s typically stored in a more optimized manner by the operating system (e.g., the OS may map it as read-only and store it in shared memory).

Code without constexpr

Just delete the constexpr keyword from the code, we obtain the 50-line assembly code, leading to larger binary size (code bloat). It differs from the prior assembly code in two aspects: Data Section Usage and Global Static Initialization

  • Data Section Usage: The code relies on .rodata and .bss sections for storing and accessing variables. The .bss is used for uninitialized data, meaning the program will need to write to these locations at runtime. Hence, the program would experience higher memory initialization overhead and possible cache inefficiencies.
  • Global Static Initialization: The code uses _GLOBAL__sub_I_main to initialize global static variables, which is executed before main() runs. Therefore, the program is slower to start.