pw_memory#
Memory utilities
Stable C++
pw_memory is a collection of low-level utilities for managing memory and
object lifetime utilities. It is similar to C++’s <memory> header, but
memory allocation and smart pointers are provided by pw_allocator
instead.
Global variables: constant initialization and binary size#
-lobal variables—variables with static storage duration—are initialized
either during compilation (constant initialization) or at runtime.
Runtime-initialized globals are initialized before main; function static
variables are initialized when the function is called the first time.
Constant initialization is guaranteed for constinit or constexpr
variables. However, the compiler may constant initialize any variable, even if
it is not constinit or constexpr constructible.
Constant initialization is usually better than runtime initialization. Constant initialization:
Reduces binary size. The binary stores initialized variable in the binary (e.g. in
.dataor.rodata), instead of the code needed to produce that data, which is typically larger.Saves CPU cycles. Initialization is a simple
memcpy.Avoids the static initialization order fiasco. Constant initialization is order-independent and occurs before static initialization.
Constant initialization may be undesirable if initialized data is larger than
the code that produces it. Variables that are initialized to all 0s are
placed in a zero-initialized segment (e.g. .bss) and never affect binary
size. Non-zero globals may increase binary size if they are constant
initialized, however.
Should I constant initialize?#
Globals should usually be constant initialized when possible. Consider the following when deciding:
If the global is zero-initialized, make it
constinitorconstexprif possible. It will not increase binary size.If the global is initialized to anything other than 0 or
nullptr, it will occupy space in the binary.If the variable is small (e.g. a few words), make it
constinitorconstexpr. The initialized variable takes space in the binary, but it probably takes less space than the code to initialize it would.If the variable is large, weigh its size against the size and runtime cost of its initialization code.
There is no hard-and-fast rule for when to constant initialize or not. The decision must be considered in light of each project’s memory layout and capabilities. Experimentation may be necessary.
Example
// This function initializes an array to non-zero values.
constexpr std::array<uint8_t, 4096> InitializedArray() {
std::array<uint8_t, 4096> data{};
for (size_t i = 0; i < data.size(); ++i) {
data[i] = static_cast<uint8_t>(i);
}
return data;
}
// This array constant initialized, which increases the binary size by 4KB.
constinit std::array<uint8_t, 4096> constant_initialized = InitializedArray();
// This array is statically initialized and takes no space in the binary, but
// the InitializedArray() function is included in the binary.
pw::RuntimeInitGlobal<std::array<uint8_t, 4096>> runtime_initialized(
InitializedArray());
// This array is zero-initialized and takes no space in the binary. It must be
// manually initialized.
std::array<uint8_t, 4096> zero_initialized;
Note
Objects cannot be split between .data and .bss. If an object contains
a single bool initialized to true followed by a 4KB array of zeroes,
it will be placed in .data, and all 4096 zeroes will be present in the
binary.
A global pw::Vector works like this. A default-initialized
pw::Vector<char, 4096> includes one non-zero uint16_t. If constant
initialized, the entire pw::Vector is stored in the binary, even though
it is mostly zeroes.
Controlling constant initialization of globals#
pw_memory offers two utilities for declaring global variables:
pw::NoDestructor – Removes the destructor, which is not necessary for globals. Constant initialization is supported, but not required.
pw::RuntimeInitGlobal – Removes the destructor. Prevents constant initialization.
It is recommended to specify constant or runtime initialization for all global variables.
Initialization |
Mutability |
Declaration |
|---|---|---|
constant |
mutable |
constinit Tconstinit pw::NoDestructor<T> |
constant |
constant |
|
runtime |
mutable |
|
runtime |
constant |
|
unspecified |
constant |
const Tconst pw::NoDestructor<T> |
unspecified |
mutable |
Tpw::NoDestructor<T> |