What you'll learn
Low-level work is safe only when representation, alignment, aliasing, ownership, and platform contracts are explicit. You will inspect bytes without violating the object model, encode data portably, call C APIs, and wrap native resources behind RAII boundaries.
By the end, you'll be able to:
- Explain object representation, padding, alignment, and byte access
- Use bit utilities and explicit endianness for binary protocols
- Distinguish trivial and standard-layout properties without overgeneralizing
- Build narrow C and operating-system interop wrappers with clear ownership
Object representation and alignment
An object occupies sizeof(T) bytes at an address satisfyingalignof(T). Its object representation includes value bytes and possible padding. Padding values may be indeterminate, so bytewise equality of class objects does not imply semantic equality. std::byte represents raw storage without pretending it is a character or number.
#include <cstddef>
#include <iostream>
#include <span>
struct PacketHeader {
std::uint16_t kind;
std::uint32_t length;
};
int main() {
PacketHeader header{7, 1024};
auto bytes{std::as_bytes(std::span{&header, 1})};
std::cout << "size: " << sizeof(header) << '\n';
std::cout << "alignment: " << alignof(PacketHeader) << '\n';
std::cout << "representation bytes: " << bytes.size() << '\n';
}Watch out
Bits and endianness
Unsigned integers are the clearest base for bit masks and shifts. The <bit>header provides rotations, population counts, powers-of-two helpers, bit_cast, and native endianness detection. A protocol must select a byte order and encode each field explicitly rather than copying native integers into a buffer.
#include <array>
#include <cstdint>
#include <iomanip>
#include <iostream>
std::array<std::uint8_t, 4> encode_big_endian(std::uint32_t value) {
return {
static_cast<std::uint8_t>(value >> 24),
static_cast<std::uint8_t>(value >> 16),
static_cast<std::uint8_t>(value >> 8),
static_cast<std::uint8_t>(value)
};
}
int main() {
for (auto byte : encode_big_endian(0x12345678u)) {
std::cout << std::hex << static_cast<unsigned>(byte) << ' ';
}
}Key idea
Trivial and standard-layout types
Type traits describe specific language properties. Trivially copyable types may have their representations copied with memcpy and restored under defined conditions. Standard-layout types have layout guarantees useful for some C interop. Neither property alone makes raw serialization portable or makes every byte pattern a valid value.
| Property | Useful guarantee | Does not guarantee |
|---|---|---|
| Trivially copyable | Representation can be copied as bytes | Stable cross-platform format |
| Standard layout | Restricted, C-like member layout rules | No padding |
| Unique object representations | No distinct representations for equal values | Semantic protocol validity |
| Implicit lifetime | Certain operations may begin object lifetime in storage | Arbitrary type punning |
Aliasing and safe representation access
Accessing storage through an unrelated object type can violate strict-aliasing and lifetime rules. Use std::bit_cast for same-size trivially copyable value reinterpretation,memcpy for representation transfer, and character or byte views for inspection. Do not use reinterpret_cast as a universal conversion license.
#include <bit>
#include <cstdint>
#include <iomanip>
#include <iostream>
int main() {
float value{1.0f};
static_assert(sizeof(float) == sizeof(std::uint32_t));
auto bits{std::bit_cast<std::uint32_t>(value)};
std::cout << "0x" << std::hex << bits << '\n';
}Note
volatile is for specific observable accesses such as memory-mapped device registers. It is not thread synchronization and does not make compound operations atomic.C interoperability
C APIs expose functions, pointers, buffers, error codes, callbacks, and opaque handles.extern "C" requests C language linkage for declarations; it does not convert C semantics into C++. Keep the boundary narrow, validate sizes and nullability, and translate raw errors and ownership into C++ types immediately.
// C header
#ifdef __cplusplus
extern "C" {
#endif
struct library_handle;
library_handle* library_open(const char* path);
void library_close(library_handle*);
#ifdef __cplusplus
}
#endif
// C++ wrapper
#include <memory>
#include <stdexcept>
struct CloseLibrary {
void operator()(library_handle* handle) const noexcept {
if (handle) library_close(handle);
}
};
using Library = std::unique_ptr<library_handle, CloseLibrary>;
Library open_library(const char* path) {
Library result{library_open(path)};
if (!result) throw std::runtime_error{"library_open failed"};
return result;
}Tip
Native resources and platform APIs
File descriptors, sockets, windows, mappings, and device handles each have distinct invalid values and release functions. Give each one a move-only RAII wrapper, keep platform headers behind source-file boundaries, and expose standard C++ values, spans, paths, durations, and error types to the rest of the application.
- Store the precise native handle type, not an integer chosen for convenience
- Represent invalid/empty state explicitly and make destruction idempotent
- Delete copying and implement noexcept moves for exclusive resources
- Validate buffer pointer-plus-length pairs before calling native code
- Unmap memory before its backing handle or file is released
- Centralize feature detection and platform-specific compilation
Watch out
Recap & quick check
Key takeaways
- Object representation includes possible padding and is not a portable serialization format.
- Binary protocols require explicit widths, byte order, bounds, and validation.
- Trivially copyable and standard-layout are narrow properties, not universal safety labels.
- bit_cast, memcpy, and byte views are safer than unrelated-type pointer dereferencing.
- C and platform boundaries should be narrow adapters that immediately establish RAII and typed contracts.
Quick check
1. Does sizeof(struct) equal the sum of member sizes?
2. What should a binary format specify?
3. What is std::bit_cast for?
4. Does volatile synchronize threads?
Phase 4 complete. Next, Module 26 — Debugging, Testing & Undefined Behavior begins the professional engineering phase.