memory-safety-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMemory Safety Patterns
内存安全编程模式
Cross-language patterns for memory-safe programming including RAII, ownership, smart pointers, and resource management.
跨语言内存安全编程模式,涵盖RAII、所有权、智能指针和资源管理。
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
- 编写内存安全的系统代码
- 管理资源(文件、套接字、内存)
- 防止野指针访问和内存泄漏
- 实现RAII模式
- 为安全性选择合适的编程语言
- 调试内存问题
Core Concepts
核心概念
1. Memory Bug Categories
1. 内存漏洞分类
| 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 |
| 漏洞类型 | 描述 | 预防方案 |
|---|---|---|
| Use-after-free | 访问已释放的内存 | 所有权机制、RAII |
| Double-free | 重复释放同一块内存 | 智能指针 |
| Memory leak | 内存从未被释放 | RAII、垃圾回收 |
| Buffer overflow | 写入超出缓冲区边界 | 边界检查 |
| Dangling pointer | 指向已释放内存的指针 | 生命周期追踪 |
| Data race | 并发环境下无同步的访问 | 所有权机制、Sync |
2. Safety Spectrum
2. 安全性谱系
Manual (C) → Smart Pointers (C++) → Ownership (Rust) → GC (Go, Java)
Less safe More safe
More control Less controlManual (C) → Smart Pointers (C++) → Ownership (Rust) → GC (Go, Java)
Less safe More safe
More control Less controlPatterns by Language
各语言实现模式
Pattern 1: RAII in C++
模式1:C++中的RAII
cpp
// RAII: Resource Acquisition Is Initialization
// Resource lifetime tied to object lifetime
#include <memory>
#include <fstream>
#include <mutex>
// File handle with RAII
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: file_(path) {
if (!file_.is_open()) {
throw std::runtime_error("Failed to open file");
}
}
// Destructor automatically closes file
~FileHandle() = default; // fstream closes in its destructor
// Delete copy (prevent double-close)
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// Allow move
FileHandle(FileHandle&&) = default;
FileHandle& operator=(FileHandle&&) = default;
void write(const std::string& data) {
file_ << data;
}
private:
std::fstream file_;
};
// Lock guard (RAII for mutexes)
class Database {
public:
void update(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_); // Released on scope exit
data_[key] = value;
}
std::string get(const std::string& key) {
std::shared_lock<std::shared_mutex> lock(shared_mutex_);
return data_[key];
}
private:
std::mutex mutex_;
std::shared_mutex shared_mutex_;
std::map<std::string, std::string> data_;
};
// Transaction with rollback (RAII)
template<typename T>
class Transaction {
public:
explicit Transaction(T& target)
: target_(target), backup_(target), committed_(false) {}
~Transaction() {
if (!committed_) {
target_ = backup_; // Rollback
}
}
void commit() { committed_ = true; }
T& get() { return target_; }
private:
T& target_;
T backup_;
bool committed_;
};cpp
// RAII: Resource Acquisition Is Initialization
// Resource lifetime tied to object lifetime
#include <memory>
#include <fstream>
#include <mutex>
// File handle with RAII
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: file_(path) {
if (!file_.is_open()) {
throw std::runtime_error("Failed to open file");
}
}
// Destructor automatically closes file
~FileHandle() = default; // fstream closes in its destructor
// Delete copy (prevent double-close)
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// Allow move
FileHandle(FileHandle&&) = default;
FileHandle& operator=(FileHandle&&) = default;
void write(const std::string& data) {
file_ << data;
}
private:
std::fstream file_;
};
// Lock guard (RAII for mutexes)
class Database {
public:
void update(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_); // Released on scope exit
data_[key] = value;
}
std::string get(const std::string& key) {
std::shared_lock<std::shared_mutex> lock(shared_mutex_);
return data_[key];
}
private:
std::mutex mutex_;
std::shared_mutex shared_mutex_;
std::map<std::string, std::string> data_;
};
// Transaction with rollback (RAII)
template<typename T>
class Transaction {
public:
explicit Transaction(T& target)
: target_(target), backup_(target), committed_(false) {}
~Transaction() {
if (!committed_) {
target_ = backup_; // Rollback
}
}
void commit() { committed_ = true; }
T& get() { return target_; }
private:
T& target_;
T backup_;
bool committed_;
};Pattern 2: Smart Pointers in C++
模式2:C++中的智能指针
cpp
#include <memory>
// unique_ptr: Single ownership
class Engine {
public:
void start() { /* ... */ }
};
class Car {
public:
Car() : engine_(std::make_unique<Engine>()) {}
void start() {
engine_->start();
}
// Transfer ownership
std::unique_ptr<Engine> extractEngine() {
return std::move(engine_);
}
private:
std::unique_ptr<Engine> engine_;
};
// shared_ptr: Shared ownership
class Node {
public:
std::string data;
std::shared_ptr<Node> next;
// Use weak_ptr to break cycles
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; // Weak reference prevents cycle
// Access weak_ptr
if (auto parent = node2->parent.lock()) {
// parent is valid shared_ptr
}
}
// Custom deleter for resources
class Socket {
public:
static void close(int* fd) {
if (fd && *fd >= 0) {
::close(*fd);
delete fd;
}
}
};
auto createSocket() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
return std::unique_ptr<int, decltype(&Socket::close)>(
new int(fd),
&Socket::close
);
}
// make_unique/make_shared best practices
void bestPractices() {
// Good: Exception safe, single allocation
auto ptr = std::make_shared<Widget>();
// Bad: Two allocations, not exception safe
std::shared_ptr<Widget> ptr2(new Widget());
// For arrays
auto arr = std::make_unique<int[]>(10);
}cpp
#include <memory>
// unique_ptr: Single ownership
class Engine {
public:
void start() { /* ... */ }
};
class Car {
public:
Car() : engine_(std::make_unique<Engine>()) {}
void start() {
engine_->start();
}
// Transfer ownership
std::unique_ptr<Engine> extractEngine() {
return std::move(engine_);
}
private:
std::unique_ptr<Engine> engine_;
};
// shared_ptr: Shared ownership
class Node {
public:
std::string data;
std::shared_ptr<Node> next;
// Use weak_ptr to break cycles
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; // Weak reference prevents cycle
// Access weak_ptr
if (auto parent = node2->parent.lock()) {
// parent is valid shared_ptr
}
}
// Custom deleter for resources
class Socket {
public:
static void close(int* fd) {
if (fd && *fd >= 0) {
::close(*fd);
delete fd;
}
}
};
auto createSocket() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
return std::unique_ptr<int, decltype(&Socket::close)>(
new int(fd),
&Socket::close
);
}
// make_unique/make_shared best practices
void bestPractices() {
// Good: Exception safe, single allocation
auto ptr = std::make_shared<Widget>();
// Bad: Two allocations, not exception safe
std::shared_ptr<Widget> ptr2(new Widget());
// For arrays
auto arr = std::make_unique<int[]>(10);
}Pattern 3: Ownership in Rust
模式3:Rust中的所有权机制
rust
// Move semantics (default)
fn move_example() {
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED, no longer valid
// println!("{}", s1); // Compile error!
println!("{}", s2);
}
// Borrowing (references)
fn borrow_example() {
let s = String::from("hello");
// Immutable borrow (multiple allowed)
let len = calculate_length(&s);
println!("{} has length {}", s, len);
// Mutable borrow (only one allowed)
let mut s = String::from("hello");
change(&mut s);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but doesn't drop since borrowed
fn change(s: &mut String) {
s.push_str(", world");
}
// Lifetimes: Compiler tracks reference validity
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struct with references needs lifetime annotation
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
// Lifetime elision: compiler infers 'a for &self
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
// Interior mutability
use std::cell::{Cell, RefCell};
use std::rc::Rc;
struct Stats {
count: Cell<i32>, // Copy types
data: RefCell<Vec<String>>, // Non-Copy types
}
impl Stats {
fn increment(&self) {
self.count.set(self.count.get() + 1);
}
fn add_data(&self, item: String) {
self.data.borrow_mut().push(item);
}
}
// Rc for shared ownership (single-threaded)
fn rc_example() {
let data = Rc::new(vec![1, 2, 3]);
let data2 = Rc::clone(&data); // Increment reference count
println!("Count: {}", Rc::strong_count(&data)); // 2
}
// Arc for shared ownership (thread-safe)
use std::sync::Arc;
use std::thread;
fn arc_example() {
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..3)
.map(|_| {
let data = Arc::clone(&data);
thread::spawn(move || {
println!("{:?}", data);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}rust
// Move semantics (default)
fn move_example() {
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED, no longer valid
// println!("{}", s1); // Compile error!
println!("{}", s2);
}
// Borrowing (references)
fn borrow_example() {
let s = String::from("hello");
// Immutable borrow (multiple allowed)
let len = calculate_length(&s);
println!("{} has length {}", s, len);
// Mutable borrow (only one allowed)
let mut s = String::from("hello");
change(&mut s);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but doesn't drop since borrowed
fn change(s: &mut String) {
s.push_str(", world");
}
// Lifetimes: Compiler tracks reference validity
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Struct with references needs lifetime annotation
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
// Lifetime elision: compiler infers 'a for &self
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
// Interior mutability
use std::cell::{Cell, RefCell};
use std::rc::Rc;
struct Stats {
count: Cell<i32>, // Copy types
data: RefCell<Vec<String>>, // Non-Copy types
}
impl Stats {
fn increment(&self) {
self.count.set(self.count.get() + 1);
}
fn add_data(&self, item: String) {
self.data.borrow_mut().push(item);
}
}
// Rc for shared ownership (single-threaded)
fn rc_example() {
let data = Rc::new(vec![1, 2, 3]);
let data2 = Rc::clone(&data); // Increment reference count
println!("Count: {}", Rc::strong_count(&data)); // 2
}
// Arc for shared ownership (thread-safe)
use std::sync::Arc;
use std::thread;
fn arc_example() {
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..3)
.map(|_| {
let data = Arc::clone(&data);
thread::spawn(move || {
println!("{:?}", data);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}Pattern 4: Safe Resource Management in C
模式4:C中的安全资源管理
c
// C doesn't have RAII, but we can use patterns
#include <stdlib.h>
#include <stdio.h>
// Pattern: goto cleanup
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;
}
// Process file...
result = 0;
cleanup:
if (buffer) free(buffer);
if (file) fclose(file);
return result;
}
// Pattern: Opaque pointer with create/destroy
typedef struct Context Context;
Context* context_create(void);
void context_destroy(Context* ctx);
int context_process(Context* ctx, const char* data);
// Implementation
struct Context {
int* data;
size_t size;
FILE* log;
};
Context* context_create(void) {
Context* ctx = calloc(1, sizeof(Context));
if (!ctx) return NULL;
ctx->data = malloc(100 * sizeof(int));
if (!ctx->data) {
free(ctx);
return NULL;
}
ctx->log = fopen("log.txt", "w");
if (!ctx->log) {
free(ctx->data);
free(ctx);
return NULL;
}
return ctx;
}
void context_destroy(Context* ctx) {
if (ctx) {
if (ctx->log) fclose(ctx->log);
if (ctx->data) free(ctx->data);
free(ctx);
}
}
// Pattern: Cleanup attribute (GCC/Clang extension)
#define AUTO_FREE __attribute__((cleanup(auto_free_func)))
void auto_free_func(void** ptr) {
free(*ptr);
}
void auto_free_example(void) {
AUTO_FREE char* buffer = malloc(1024);
// buffer automatically freed at end of scope
}c
// C doesn't have RAII, but we can use patterns
#include <stdlib.h>
#include <stdio.h>
// Pattern: goto cleanup
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;
}
// Process file...
result = 0;
cleanup:
if (buffer) free(buffer);
if (file) fclose(file);
return result;
}
// Pattern: Opaque pointer with create/destroy
typedef struct Context Context;
Context* context_create(void);
void context_destroy(Context* ctx);
int context_process(Context* ctx, const char* data);
// Implementation
struct Context {
int* data;
size_t size;
FILE* log;
};
Context* context_create(void) {
Context* ctx = calloc(1, sizeof(Context));
if (!ctx) return NULL;
ctx->data = malloc(100 * sizeof(int));
if (!ctx->data) {
free(ctx);
return NULL;
}
ctx->log = fopen("log.txt", "w");
if (!ctx->log) {
free(ctx->data);
free(ctx);
return NULL;
}
return ctx;
}
void context_destroy(Context* ctx) {
if (ctx) {
if (ctx->log) fclose(ctx->log);
if (ctx->data) free(ctx->data);
free(ctx);
}
}
// Pattern: Cleanup attribute (GCC/Clang extension)
#define AUTO_FREE __attribute__((cleanup(auto_free_func)))
void auto_free_func(void** ptr) {
free(*ptr);
}
void auto_free_example(void) {
AUTO_FREE char* buffer = malloc(1024);
// buffer automatically freed at end of scope
}Pattern 5: Bounds Checking
模式5:边界检查
cpp
// C++: Use containers instead of raw arrays
#include <vector>
#include <array>
#include <span>
void safe_array_access() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// Safe: throws std::out_of_range
try {
int val = vec.at(10);
} catch (const std::out_of_range& e) {
// Handle error
}
// Unsafe but faster (no bounds check)
int val = vec[2];
// Modern C++20: std::span for array views
std::span<int> view(vec);
// Iterators are bounds-safe
for (int& x : view) {
x *= 2;
}
}
// Fixed-size arrays
void fixed_array() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// Compile-time size known
static_assert(arr.size() == 5);
// Safe access
int val = arr.at(2);
}rust
// Rust: Bounds checking by default
fn rust_bounds_checking() {
let vec = vec![1, 2, 3, 4, 5];
// Runtime bounds check (panics if out of bounds)
let val = vec[2];
// Explicit option (no panic)
match vec.get(10) {
Some(val) => println!("Got {}", val),
None => println!("Index out of bounds"),
}
// Iterators (no bounds checking needed)
for val in &vec {
println!("{}", val);
}
// Slices are bounds-checked
let slice = &vec[1..3]; // [2, 3]
}cpp
// C++: Use containers instead of raw arrays
#include <vector>
#include <array>
#include <span>
void safe_array_access() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// Safe: throws std::out_of_range
try {
int val = vec.at(10);
} catch (const std::out_of_range& e) {
// Handle error
}
// Unsafe but faster (no bounds check)
int val = vec[2];
// Modern C++20: std::span for array views
std::span<int> view(vec);
// Iterators are bounds-safe
for (int& x : view) {
x *= 2;
}
}
// Fixed-size arrays
void fixed_array() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// Compile-time size known
static_assert(arr.size() == 5);
// Safe access
int val = arr.at(2);
}rust
// Rust: Bounds checking by default
fn rust_bounds_checking() {
let vec = vec![1, 2, 3, 4, 5];
// Runtime bounds check (panics if out of bounds)
let val = vec[2];
// Explicit option (no panic)
match vec.get(10) {
Some(val) => println!("Got {}", val),
None => println!("Index out of bounds"),
}
// Iterators (no bounds checking needed)
for val in &vec {
println!("{}", val);
}
// Slices are bounds-checked
let slice = &vec[1..3]; // [2, 3]
}Pattern 6: Preventing Data Races
模式6:防止数据竞争
cpp
// C++: Thread-safe shared state
#include <mutex>
#include <shared_mutex>
#include <atomic>
class ThreadSafeCounter {
public:
void increment() {
// Atomic operations
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()) {
return it->second;
}
return std::nullopt;
}
private:
mutable std::shared_mutex mutex_;
std::map<std::string, int> data_;
};rust
// Rust: Data race prevention at compile time
use std::sync::{Arc, Mutex, RwLock};
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread;
// Atomic for simple types
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));
}
// Mutex for complex types
fn mutex_example() {
let data = Arc::new(Mutex::new(vec![]));
let handles: Vec<_> = (0..10)
.map(|i| {
let data = Arc::clone(&data);
thread::spawn(move || {
let mut vec = data.lock().unwrap();
vec.push(i);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
// RwLock for read-heavy workloads
fn rwlock_example() {
let data = Arc::new(RwLock::new(HashMap::new()));
// Multiple readers OK
let read_guard = data.read().unwrap();
// Writer blocks readers
let write_guard = data.write().unwrap();
}cpp
// C++: Thread-safe shared state
#include <mutex>
#include <shared_mutex>
#include <atomic>
class ThreadSafeCounter {
public:
void increment() {
// Atomic operations
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()) {
return it->second;
}
return std::nullopt;
}
private:
mutable std::shared_mutex mutex_;
std::map<std::string, int> data_;
};rust
// Rust: Data race prevention at compile time
use std::sync::{Arc, Mutex, RwLock};
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread;
use std::collections::HashMap;
// Atomic for simple types
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));
}
// Mutex for complex types
fn mutex_example() {
let data = Arc::new(Mutex::new(vec![]));
let handles: Vec<_> = (0..10)
.map(|i| {
let data = Arc::clone(&data);
thread::spawn(move || {
let mut vec = data.lock().unwrap();
vec.push(i);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
// RwLock for read-heavy workloads
fn rwlock_example() {
let data = Arc::new(RwLock::new(HashMap::new()));
// Multiple readers OK
let read_guard = data.read().unwrap();
// Writer blocks readers
let write_guard = data.write().unwrap();
}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
- 优先使用RAII - 将资源生命周期与作用域绑定
- 使用智能指针 - 在C++中避免使用原始指针
- 理解所有权机制 - 明确资源的归属方
- 检查边界 - 使用安全的访问方式
- 借助工具 - 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 carelessly - In Rust, minimize it
unsafe - Don't assume thread safety - Be explicit
- 不要使用原始指针 - 除非与C代码交互
- 不要返回局部变量的引用 - 会产生悬垂指针
- 不要忽略编译器警告 - 它们能提前发现漏洞
- 不要随意使用- 在Rust中尽量减少其使用
unsafe - 不要假设线程安全 - 需显式保证
Debugging Tools
调试工具
bash
undefinedbash
undefinedAddressSanitizer (Clang/GCC)
AddressSanitizer (Clang/GCC)
clang++ -fsanitize=address -g source.cpp
clang++ -fsanitize=address -g source.cpp
Valgrind
Valgrind
valgrind --leak-check=full ./program
valgrind --leak-check=full ./program
Rust Miri (undefined behavior detector)
Rust Miri (undefined behavior detector)
cargo +nightly miri run
cargo +nightly miri run
ThreadSanitizer
ThreadSanitizer
clang++ -fsanitize=thread -g source.cpp
undefinedclang++ -fsanitize=thread -g source.cpp
undefined