| name | cpp-memory-management |
| description | Use when writing, reviewing, or refactoring C++ code involving memory ownership, lifetime management, smart pointers, RAII, custom allocators, or memory safety in automotive embedded projects (IVI, HUD, RSE on Linux/QNX/Android NDK). Covers Rule of Zero/Five, unique_ptr/shared_ptr, weak_ptr, placement new, arena allocators, AddressSanitizer, and common memory bug patterns.
|
| argument-hint | <class-or-module> [write|review|audit] |
C++ Memory Management — Embedded Automotive
Expert practices for safe, deterministic, and efficient memory management in
C++14–C++20 embedded automotive code running on Linux, QNX, and Android NDK.
When to Use This Skill
- Designing ownership model for a new class or subsystem.
- Reviewing code for raw pointer misuse, leaks, or dangling references.
- Replacing manual
new/delete with RAII idioms.
- Designing a component that must avoid heap allocation in real-time paths.
- Auditing code for MISRA C++ violations related to memory.
- Investigating a crash suspected to be a use-after-free or heap corruption.
Ownership Vocabulary
| Concept | Meaning |
|---|
| Owner | Entity responsible for deleting the resource |
| Non-owning pointer/reference | Observes but never deletes |
| Unique ownership | Exactly one owner (std::unique_ptr) |
| Shared ownership | Multiple owners via reference counting (std::shared_ptr) |
| Weak reference | Observes shared-owned resource without extending lifetime (std::weak_ptr) |
Rule of thumb: 99% of C++ code should have zero explicit delete calls. Use smart pointers.
The Rule of Zero / Three / Five
class AudioTrack {
std::string name_;
std::vector<int16_t> samples_;
std::unique_ptr<Codec> codec_;
};
class SharedMemBuffer {
public:
explicit SharedMemBuffer(std::size_t size);
~SharedMemBuffer();
SharedMemBuffer(const SharedMemBuffer&) = delete;
SharedMemBuffer& operator=(const SharedMemBuffer&) = delete;
SharedMemBuffer(SharedMemBuffer&& other) noexcept;
SharedMemBuffer& operator=(SharedMemBuffer&& other) noexcept;
private:
void* ptr_ = nullptr;
std::size_t size_ = 0;
};
Never define only some of these — define all or none (use = default / = delete).
Smart Pointers
std::unique_ptr — Default Choice
auto codec = std::make_unique<AacCodec>(AacCodec::Profile::kLC);
auto buffers = std::make_unique<int16_t[]>(1024);
void processTrack(std::unique_ptr<AudioTrack> track);
std::unique_ptr<IDecoder> createDecoder(Codec codec) {
switch (codec) {
case Codec::kAac: return std::make_unique<AacDecoder>();
case Codec::kMp3: return std::make_unique<Mp3Decoder>();
default: return nullptr;
}
}
std::shared_ptr — Shared Ownership
auto config = std::make_shared<AudioConfig>(sampleRate, channels);
AudioPlayer player(config);
AudioRecorder recorder(config);
auto dbus_conn = std::shared_ptr<DBusConnection>(
dbus_bus_get(DBUS_BUS_SESSION, nullptr),
[](DBusConnection* c) { dbus_connection_unref(c); }
);
std::weak_ptr — Breaking Cycles
class Widget {
std::shared_ptr<Widget> parent_;
std::weak_ptr<Widget> parent_;
public:
void doSomethingWithParent() {
if (auto p = parent_.lock()) {
p->notify();
}
}
};
Raw Pointers — When Allowed
void adjustVolume(AudioManager* mgr, int delta);
void processBuffer(const int16_t* data, std::size_t len);
RAII — Resource Acquisition Is Initialization
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: fd_(::open(path.c_str(), O_RDONLY))
{
if (fd_ < 0) throw std::system_error(errno, std::system_category());
}
~FileHandle() { if (fd_ >= 0) ::close(fd_); }
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&&) = default;
int get() const noexcept { return fd_; }
private:
int fd_;
};
{
std::lock_guard<std::mutex> lock(mutex_);
}
template<typename F>
struct ScopeGuard {
F fn;
explicit ScopeGuard(F f) : fn(std::move(f)) {}
~ScopeGuard() { fn(); }
ScopeGuard(const ScopeGuard&) = delete;
};
template<typename F>
auto makeScopeGuard(F f) { return ScopeGuard<F>{std::move(f)}; }
auto guard = makeScopeGuard([&] { cleanup(); });
Avoiding Heap Allocation in Real-Time Paths
For QNX hard real-time and RTOS-like components on Linux:
class RealTimeAudioProcessor {
public:
explicit RealTimeAudioProcessor(std::size_t maxFrames)
: buffer_(maxFrames) // vector allocated ONCE in constructor
, scratchpad_(maxFrames)
{}
void process(const int16_t* in, int16_t* out, std::size_t frames) noexcept {
}
private:
std::vector<int16_t> buffer_;
std::vector<int16_t> scratchpad_;
};
template<typename T, std::size_t N>
class RingBuffer {
std::array<T, N> buf_{};
std::size_t head_ = 0, tail_ = 0, count_ = 0;
public:
bool push(T val) noexcept {
if (count_ == N) return false;
buf_[tail_] = std::move(val);
tail_ = (tail_ + 1) % N;
++count_;
return true;
}
std::optional<T> pop() noexcept {
if (count_ == 0) return std::nullopt;
T val = std::move(buf_[head_]);
head_ = (head_ + 1) % N;
--count_;
return val;
}
};
AddressSanitizer (ASan) — Detecting Memory Bugs
# CMake: enable ASan for debug/test builds
if(ENABLE_ASAN)
target_compile_options(my_target PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer -g)
target_link_options(my_target PRIVATE -fsanitize=address,undefined)
endif()
ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1 ./build/my_test
ASan detects: heap/stack/global buffer overflow, use-after-free, use-after-return, double-free, memory leaks.
Common Memory Bugs and Fixes
{
auto obj = std::make_unique<Foo>();
Foo* raw = obj.get();
obj.reset();
raw->doStuff();
}
std::string& getRef() {
std::string local = "hello";
return local;
}
Foo* p = new Foo();
delete p;
delete p;
std::vector<int> v = {1, 2, 3};
for (auto it = v.begin(); it != v.end(); ++it) {
v.push_back(*it);
}
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
};
MISRA C++ Memory Rules Summary
| Rule | Requirement |
|---|
| M18-0-4 | Do not use dynamic heap memory after initialization |
| M5-3-3 | Expressions with side effects not used as delete operand |
| A18-5-2 | operator new / delete not used directly; use smart pointers |
| A18-5-8 | Objects not allocated on the heap when not necessary |
Prerequisites
- GCC 9+ or Clang 12+ toolchain installed.
- CMake 3.16+ on PATH.
- For cross-compilation: ARM/AArch64 sysroot or Android NDK r25+.
ninja build tool recommended for faster builds.
Step-by-Step Workflows
Step 1: Audit ownership
Identify each resource's lifetime; apply the Rule of Zero/Five to every class.
Step 2: Replace raw pointers with smart pointers
Use unique_ptr for exclusive ownership; shared_ptr only when shared ownership is genuinely required.
Step 3: Apply RAII
Wrap every resource acquisition in a constructor; release in the destructor.
Step 4: Run AddressSanitizer
Build with -fsanitize=address,undefined; run all tests; fix every reported issue.
Step 5: Review with clang-tidy
Enable cppcoreguidelines-* and modernize-* checks; fix owning_memory and avoid-c-arrays warnings.
Troubleshooting
- AddressSanitizer reports
heap-use-after-free — a raw pointer is used after its owning unique_ptr was destroyed; refactor to use shared_ptr / weak_ptr.
shared_ptr reference cycle causes undetected leak — use weak_ptr for back-references; verify with AddressSanitizer --detect_leaks=1.
std::bad_alloc in production — preallocate memory pools for real-time paths; avoid dynamic allocation in interrupt handlers.
- RAII destructor throwing exception — destructors must never throw; catch exceptions internally and log them.
Pre-Commit Checklist
References