| name | memory-safety-patterns |
| description | Implement memory-safe programming with RAII, ownership, smart pointers, and resource management across Rust, C++, and C. Use when writing safe systems code, managing resources, or preventing memory bugs. |
Memory Safety Patterns
Cross-language patterns for memory-safe programming including RAII, ownership, smart pointers, and resource management.
When to Use This Skill
- Writing memory-safe systems code
- Managing resources (files, sockets, memory)
- Preventing use-after-free and leaks
- Implementing RAII patterns
- Choosing between languages for safety
- Debugging memory issues
Core Concepts
1. Memory Bug Categories
| Bug Type | Description | Prevention |
|---|
| Use-after-free | Access freed memory | Ownership, RAII |
| Double-free | Free same memory twice | Smart pointers |
| Memory leak | Never free memory | RAII, GC |
| Buffer overflow | Write past buffer end | Bounds checking |
| Dangling pointer | Pointer to freed memory | Lifetime tracking |
| Data race | Concurrent unsynchronized access | Ownership, Sync |
2. Safety Spectrum
Manual (C) → Smart Pointers (C++) → Ownership (Rust) → GC (Go, Java)
Less safe More safe
More control Less control
Patterns by Language
Pattern 1: RAII in C++
#include <memory>
#include <fstream>
#include <mutex>
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: file_(path) {
if (!file_.is_open()) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandle() = default;
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&&) = default;
FileHandle& operator=(FileHandle&&) = default;
void write(const std::string& data) {
file_ << data;
}
private:
std::fstream file_;
};
class {
:
{
;
data_[key] = value;
}
{
;
data_[key];
}
:
std::mutex mutex_;
std::shared_mutex shared_mutex_;
std::map<std::string, std::string> data_;
};
< T>
{
:
}
~() {
(!committed_) {
target_ = backup_;
}
}
{ committed_ = ; }
{ target_; }
:
T& target_;
T backup_;
committed_;
};
Pattern 2: Smart Pointers in C++
#include <memory>
class Engine {
public:
void start() { }
};
class Car {
public:
Car() : engine_(std::make_unique<Engine>()) {}
void start() {
engine_->start();
}
std::unique_ptr<Engine> extractEngine() {
return std::move(engine_);
}
private:
std::unique_ptr<Engine> engine_;
};
class Node {
public:
std::string data;
std::shared_ptr<Node> next;
std::weak_ptr<Node> parent;
};
void sharedPtrExample() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->parent = node1;
if (auto parent = node2->parent.lock()) {
}
}
{
:
{
(fd && *fd >= ) {
::(*fd);
fd;
}
}
};
{
fd = (AF_INET, SOCK_STREAM, );
std::<, (&Socket::close)>(
(fd),
&Socket::close
);
}
{
ptr = std::<Widget>();
;
arr = std::<[]>();
}
Pattern 3: Ownership in Rust
fn move_example() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s2);
}
fn borrow_example() {
let s = String::from("hello");
let len = calculate_length(&s);
println!("{} has length {}", s, len);
let mut s = String::from("hello");
change(&mut s);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn change(s: &mut String) {
s.push_str();
}
<>(x: & , y: & ) & {
x.() > y.() { x } { y }
}
<> {
part: & ,
}
<> ImportantExcerpt<> {
(&) {
}
(&, announcement: &) & {
(, announcement);
.part
}
}
std::cell::{Cell, RefCell};
std::rc::Rc;
{
count: Cell<>,
data: RefCell<<>>,
}
{
(&) {
.count.(.count.() + );
}
(&, item: ) {
.data.().(item);
}
}
() {
= Rc::([, , ]);
= Rc::(&data);
(, Rc::(&data));
}
std::sync::Arc;
std::thread;
() {
= Arc::([, , ]);
: <_> = (..)
.(|_| {
= Arc::(&data);
thread::( || {
(, data);
})
})
.();
handles {
handle.().();
}
}
Pattern 4: Safe Resource Management in C
#include <stdlib.h>
#include <stdio.h>
int process_file(const char* path) {
FILE* file = NULL;
char* buffer = NULL;
int result = -1;
file = fopen(path, "r");
if (!file) {
goto cleanup;
}
buffer = malloc(1024);
if (!buffer) {
goto cleanup;
}
result = 0;
cleanup:
if (buffer) free(buffer);
if (file) fclose(file);
return result;
}
typedef struct Context Context;
Context* context_create(void);
void context_destroy(Context* ctx);
int context_process(Context* ctx, const char* data);
struct Context {
* data;
size;
FILE* ;
};
Context* {
Context* ctx = (, (Context));
(!ctx) ;
ctx->data = ( * ());
(!ctx->data) {
(ctx);
;
}
ctx-> = fopen(, );
(!ctx->) {
(ctx->data);
(ctx);
;
}
ctx;
}
{
(ctx) {
(ctx->) fclose(ctx->);
(ctx->data) (ctx->data);
(ctx);
}
}
{
(*ptr);
}
{
AUTO_FREE * buffer = ();
}
Pattern 5: Bounds Checking
#include <vector>
#include <array>
#include <span>
void safe_array_access() {
std::vector<int> vec = {1, 2, 3, 4, 5};
try {
int val = vec.at(10);
} catch (const std::out_of_range& e) {
}
int val = vec[2];
std::span<int> view(vec);
for (int& x : view) {
x *= 2;
}
}
void fixed_array() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
static_assert(arr.size() == );
val = arr.();
}
fn rust_bounds_checking() {
let vec = vec![1, 2, 3, 4, 5];
let val = vec[2];
match vec.get(10) {
Some(val) => println!("Got {}", val),
None => println!("Index out of bounds"),
}
for val in &vec {
println!("{}", val);
}
let slice = &vec[1..3];
}
Pattern 6: Preventing Data Races
#include <mutex>
#include <shared_mutex>
#include <atomic>
class ThreadSafeCounter {
public:
void increment() {
count_.fetch_add(1, std::memory_order_relaxed);
}
int get() const {
return count_.load(std::memory_order_relaxed);
}
private:
std::atomic<int> count_{0};
};
class ThreadSafeMap {
public:
void write(const std::string& key, int value) {
std::unique_lock lock(mutex_);
data_[key] = value;
}
std::optional<int> read(const std::string& key) {
std::shared_lock lock(mutex_);
auto it = data_.find(key);
if (it != data_.end()) {
it->second;
}
std::;
}
:
std::shared_mutex mutex_;
std::map<std::string, > data_;
};
use std::sync::{Arc, Mutex, RwLock};
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread;
fn atomic_example() {
let counter = Arc::new(AtomicI32::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let counter = Arc::clone(&counter);
thread::spawn(move || {
counter.fetch_add(1, Ordering::SeqCst);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
println!("Counter: {}", counter.load(Ordering::SeqCst));
}
fn mutex_example() {
let data = Arc::new(Mutex::new(vec![]));
let handles: Vec<_> = (0..)
.(|i| {
= Arc::(&data);
thread::( || {
= data.().();
vec.(i);
})
})
.();
handles {
handle.().();
}
}
() {
= Arc::(RwLock::(HashMap::()));
= data.().();
= data.().();
}
Best Practices
Do's
- Prefer RAII - Tie resource lifetime to scope
- Use smart pointers - Avoid raw pointers in C++
- Understand ownership - Know who owns what
- Check bounds - Use safe access methods
- Use tools - AddressSanitizer, Valgrind, Miri
Don'ts
- Don't use raw pointers - Unless interfacing with C
- Don't return local references - Dangling pointer
- Don't ignore compiler warnings - They catch bugs
- Don't use
unsafe carelessly - In Rust, minimize it
- Don't assume thread safety - Be explicit
Debugging Tools
clang++ -fsanitize=address -g source.cpp
valgrind --leak-check=full ./program
cargo +nightly miri run
clang++ -fsanitize=thread -g source.cpp
Resources