Master the skills of identifying, troubleshooting, and preventing C++ memory leaks, learn to use practical tools to quickly locate issues, and establish good memory management habits.
🎯 Use Cases
- • Memory Leak Detection: Continuous memory growth during program execution
- • Code Review: Preventive checks for potential memory issues
- • Performance Optimization: Addressing excessive memory usage issues
- • Tool Usage: Mastering the basic operations of memory detection tools
- • Debugging Techniques: Quickly locating and fixing memory leaks
- • Best Practices: Establishing memory-safe programming habits
📚 Basics of Memory Leaks
What is a Memory Leak
// Typical memory leak example
void memory_leak_example() {
int* ptr = new int(42); // Allocate memory
// Forget to delete ptr; // Memory leak!
std::vector* vec = new std::vector(1000);
// Forget to delete vec; // Container memory leak!
}
Hazards of Memory Leaks
- • Memory Exhaustion: Long-running programs lead to insufficient system memory
- • Performance Degradation: Frequent memory allocations cause fragmentation
- • Program Crashes: Severe cases lead to resource exhaustion
- • Server Stability: Affects long-running service programs
🔍 Common Memory Leak Patterns
1. Forgetting to Release Dynamically Allocated Memory
// ❌ Incorrect example
class BadExample {
private:
int* data_;
public:
BadExample() : data_(new int[100]) {}
// No destructor! Memory leak
// ~BadExample() { delete[] data_; } // This line should be here
};
// ✅ Correct example
class GoodExample {
private:
std::unique_ptr data_; // Automatically manages memory
public:
GoodExample() : data_(std::make_unique(100)) {}
// No need for manual destructor, smart pointer handles it automatically
};
2. Exception Safety Issues
// ❌ Exception-unsafe code
void unsafe_function() {
int* ptr = new int(42);
risky_operation(); // If an exception is thrown here
delete ptr; // This line will never execute!
}
// ✅ Exception-safe code
void safe_function() {
auto ptr = std::make_unique(42); // RAII automatically manages
risky_operation(); // Even if an exception is thrown, ptr will be automatically cleaned up
}
3. Circular Reference Issues
// ❌ Circular reference with smart pointers
class Node {
public:
std::shared_ptr next;
std::shared_ptr prev; // Circular reference!
};
void create_cycle() {
auto node1 = std::make_shared();
auto node2 = std::make_shared();
node1->next = node2;
node2->prev = node1; // Circular reference, memory leak!
}
// ✅ Break the cycle using weak_ptr
class SafeNode {
public:
std::shared_ptr next;
std::weak_ptr prev; // Use weak_ptr
};
4. Pointers in Containers Not Released
// ❌ Container storing raw pointers
std::vector pointers;
void fill_vector() {
for (int i = 0; i < 100; ++i) {
pointers.push_back(new int(i)); // Allocate memory
}
// When the container is destroyed, only the vector itself is released, not the memory pointed to by the pointers!
}
// ✅ Use smart pointer containers
std::vector<std::unique_ptr> smart_pointers;
void fill_vector_safe() {
for (int i = 0; i < 100; ++i) {
smart_pointers.push_back(std::make_unique(i));
}
// When the container is destroyed, all unique_ptr automatically release memory
}</std::unique_ptr
5. Forgetting to Pair Allocations and Deallocations
// ❌ Mismatched allocation/deallocation
void mismatched_allocation() {
int* single = new int(42);
int* array = new int[100];
delete[] single; // Error! Should use delete
delete array; // Error! Should use delete[]
}
// ✅ Correct pairing
void correct_allocation() {
int* single = new int(42);
int* array = new int[100];
delete single; // new corresponds to delete
delete[] array; // new[] corresponds to delete[]
}
🔧 Memory Leak Detection Tools
1. Valgrind (Linux/macOS)
The most powerful memory detection tool
Basic Usage
# Add debug information during compilation
g++ -g -O0 program.cpp -o program
# Use Valgrind for detection
valgrind --tool=memcheck --leak-check=full ./program
Output Interpretation
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 2 allocs, 1 frees, 404 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2E0EF: operator new(unsigned long) (in vgpreload_memcheck)
==12345== by 0x108654: main (program.cpp:10)
Key Information Interpretation:
- •
<span>400 bytes in 1 blocks are definitely lost</span>: Memory leak confirmed - •
<span>at 0x108654: main (program.cpp:10)</span>: Leak occurred at line 10 of program.cpp
2. AddressSanitizer (GCC/Clang)
Built-in detection tool in modern compilers
Usage
# Enable AddressSanitizer during compilation
g++ -fsanitize=address -g program.cpp -o program
# Run the program
./program
Practical Usage Example
// leak_test.cpp
#include
int main() {
int* leak = new int(42); // Intentionally leaking
// delete leak; // Commented out release code
return 0;
}
# Compile and run
g++ -fsanitize=address -g leak_test.cpp -o leak_test
./leak_test
# Output similar to:
# =================================================================
# ==ERROR: LeakSanitizer: detected memory leaks
#
# Direct leak of 4 byte(s) in 1 object(s) allocated from:
# #0 0x7f8b8c9b7602 in operator new(unsigned long)
# #1 0x55a8b0c01149 in main leak_test.cpp:4
3. Visual Studio Diagnostic Tools (Windows)
Memory detection on Windows platform
Enable CRT Debug Heap
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include
#endif
int main() {
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
// Your program code
int* leak = new int(42);
return 0;
}
4. Custom Simple Memory Tracker
#include
#include <map>
#include
class MemoryTracker {
private:
static std::map allocations_;
static std::mutex mutex_;
static size_t total_allocated_;
public:
static void record_allocation(void* ptr, size_t size) {
std::lock_guard lock(mutex_);
allocations_[ptr] = size;
total_allocated_ += size;
std::cout << "Allocated " << size << " bytes at " << ptr
<< " (Total: " << total_allocated_ << ")" << std::endl;
}
static void record_deallocation(void* ptr) {
std::lock_guard lock(mutex_);
auto it = allocations_.find(ptr);
if (it != allocations_.end()) {
total_allocated_ -= it->second;
std::cout << "Freed " << it->second << " bytes at " << ptr
<< " (Total: " << total_allocated_ << ")" << std::endl;
allocations_.erase(it);
}
}
static void print_leaks() {
std::lock_guard lock(mutex_);
if (!allocations_.empty()) {
std::cout << "Memory leaks detected:" << std::endl;
for (const auto& pair : allocations_) {
std::cout << " " << pair.second << " bytes at "
<< pair.first << std::endl;
}
} else {
std::cout << "No memory leaks detected!" << std::endl;
}
}
};
// Static member definition
std::map MemoryTracker::allocations_;
std::mutex MemoryTracker::mutex_;
size_t MemoryTracker::total_allocated_ = 0;
// Overload global new/delete (for debugging only)
#ifdef DEBUG_MEMORY
void* operator new(size_t size) {
void* ptr = malloc(size);
MemoryTracker::record_allocation(ptr, size);
return ptr;
}
void operator delete(void* ptr) noexcept {
MemoryTracker::record_deallocation(ptr);
free(ptr);
}
#endif</map>
🛠️ Practical Troubleshooting Techniques
1. Segmented Testing Method
// Comment out segments of the program to gradually locate the leak
int main() {
// First segment of code
// function1();
// Second segment of code
// function2();
// Third segment of code
function3(); // If only this segment has a leak, the problem is in function3
return 0;
}
2. RAII Wrapper
// Create RAII wrappers for legacy code
template
class RAIIWrapper {
private:
T* ptr_;
public:
explicit RAIIWrapper(T* p) : ptr_(p) {}
~RAIIWrapper() { delete ptr_; }
T* get() const { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
// Disable copy
RAIIWrapper(const RAIIWrapper&) = delete;
RAIIWrapper& operator=(const RAIIWrapper&) = delete;
};
// Usage example
void legacy_code_wrapper() {
RAIIWrapper wrapper(new int(42));
// Even if an exception is thrown, wrapper will automatically delete on destruction
risky_operation();
}
3. Smart Pointer Conversion Strategy
// Gradually convert raw pointers to smart pointers
class ModernClass {
private:
// Step 1: Change member variables to smart pointers
std::unique_ptr data_;
// int* data_; // Old code
public:
ModernClass(size_t size)
: data_(std::make_unique(size)) {
// : data_(new int[size]) { // Old code
}
// Step 2: Provide compatibility interface
int* get_data() const { return data_.get(); }
// Step 3: Gradually replace all usage points
void process() {
// work_with_raw_pointer(data_.get()); // Transition period
work_with_smart_pointer(data_); // Final goal
}
};
🚀 Prevention Strategies
1. Prefer Smart Pointers
// ✅ Recommended memory management method
class RecommendedPractice {
private:
std::unique_ptr resource_;
std::shared_ptr shared_resource_;
std::vector<std::unique_ptr> items_;
public:
RecommendedPractice()
: resource_(std::make_unique()),
shared_resource_(std::make_shared()) {}
void add_item(std::unique_ptr item) {
items_.push_back(std::move(item));
}
// No need for manual destructor, smart pointers manage automatically
};</std::unique_ptr
2. Use Containers Instead of Raw Arrays
// ❌ Avoid using raw arrays
void avoid_raw_arrays() {
int* raw_array = new int[100]; // Needs manual management
// ... Use array
delete[] raw_array;
}
// ✅ Use standard containers
void use_containers() {
std::vector container(100); // Automatically manages memory
// ... Use container
// Automatically released at the end of the function
}
3. Exception-Safe Resource Management
// Resource Acquisition Is Initialization (RAII)
class SafeFileHandler {
private:
std::unique_ptr file_;
public:
explicit SafeFileHandler(const char* filename)
: file_(fopen(filename, "r"), &fclose) {
if (!file_) {
throw std::runtime_error("Failed to open file");
}
}
FILE* get() const { return file_.get(); }
// Automatically closes the file on destruction
};
4. Factory Function Pattern
// Use factory functions to create objects
template
std::unique_ptr make_safe(Args&&... args) {
return std::make_unique(std::forward(args)...);
}
// Usage example
auto obj = make_safe(param1, param2);
// Automatically manages memory, no need for manual delete
🔍 Case Study Analysis
Case 1: Memory Leak in Image Processing Program
// Problematic code
class ImageProcessor {
private:
unsigned char* image_data_;
int width_, height_;
public:
bool load_image(const std::string& filename) {
// Load image data
image_data_ = new unsigned char[width_ * height_ * 3];
// ... Loading logic
return true;
}
void process_image() {
if (!image_data_) return;
// Create temporary buffer
unsigned char* temp = new unsigned char[width_ * height_ * 3];
// Process image...
if (some_error_condition) {
return; // Leak! Forget to release temp
}
// Update image data
delete[] image_data_;
image_data_ = temp;
}
};
// Fixed code
class SafeImageProcessor {
private:
std::vector image_data_;
int width_, height_;
public:
bool load_image(const std::string& filename) {
image_data_.resize(width_ * height_ * 3);
// ... Loading logic
return true;
}
void process_image() {
if (image_data_.empty()) return;
// Use vector to automatically manage memory
std::vector temp(width_ * height_ * 3);
// Process image...
if (some_error_condition) {
return; // Safe! temp automatically releases
}
// Move semantics to avoid copy
image_data_ = std::move(temp);
}
};
Case 2: Memory Leak in Connection Pool
// Problematic code
class ConnectionPool {
private:
std::queue available_connections_;
std::set all_connections_;
public:
Connection* get_connection() {
if (available_connections_.empty()) {
auto* conn = new Connection(); // Potential leak
all_connections_.insert(conn);
return conn;
}
auto* conn = available_connections_.front();
available_connections_.pop();
return conn;
}
void return_connection(Connection* conn) {
available_connections_.push(conn);
}
// Destructor may be forgotten to implement!
};
// Fixed code
class SafeConnectionPool {
private:
std::queue<std::shared_ptr> available_connections_;
std::vector<std::shared_ptr> all_connections_;
public:
std::shared_ptr get_connection() {
if (available_connections_.empty()) {
auto conn = std::make_shared();
all_connections_.push_back(conn);
return conn;
}
auto conn = available_connections_.front();
available_connections_.pop();
return conn;
}
void return_connection(std::shared_ptr conn) {
available_connections_.push(conn);
}
// No need for manual destructor, smart pointers manage automatically
};</std::shared_ptr</std::shared_ptr
📋 Memory Leak Check List
Code Review Points
- • Dynamic Allocation Check: Each
<span>new</span>has a corresponding<span>delete</span> - • Array Allocation Check:
<span>new[]</span>corresponds to<span>delete[]</span> - • Exception Safety Check: Is memory correctly released on exception paths
- • Smart Pointer Usage: Prefer using
<span>unique_ptr</span>and<span>shared_ptr</span> - • Circular Reference Check: Are there circular references with
<span>shared_ptr</span> - • Container Cleanup: Are pointers in containers correctly released
Tool Usage Check
- • Compilation Options: Use
<span>-fsanitize=address</span>for compilation - • Debug Information: Include
<span>-g</span>option during compilation - • Memory Tools: Regularly use Valgrind or similar tools for detection
- • Unit Testing: Write dedicated tests for memory management
Design Principles Check
- • RAII Principle: Resource Acquisition Is Initialization
- • Smart Pointer Priority: Avoid using raw pointers for memory management
- • Container Priority: Use STL containers instead of raw arrays
- • Exception Safety: Ensure strong exception safety
💡 Best Practices Summary
✅ Recommended Practices
- 1. Smart Pointers First: Prefer using
<span>unique_ptr</span>and<span>shared_ptr</span> - 2. RAII Principle: Bind resource management to object lifecycle
- 3. Containers Instead of Arrays: Use
<span>std::vector</span>and other containers - 4. Tool Assistance: Regularly use memory detection tools
- 5. Exception Safety: Ensure resources are correctly released on exception paths
- 6. Circular Reference Handling: Use
<span>weak_ptr</span>to break<span>shared_ptr</span>cycles
❌ Avoid Traps
- 1. Forgetting to Pair Releases:
<span>new/delete</span>and<span>new[]/delete[]</span>must be paired - 2. Exception Path Leaks: Be cautious of memory release on exception throws
- 3. Smart Pointer Circular References: Be careful of circular references with
<span>shared_ptr</span> - 4. Container Pointer Leaks: Be particularly careful when storing raw pointers in containers
- 5. Mixed Memory Management: Do not mix
<span>malloc/free</span>and<span>new/delete</span>
🤔 Thought Questions
- 1. How to detect and fix memory leaks in the following code?
std::vector create_strings() {
std::vector result;
for (int i = 0; i < 10; ++i) {
result.push_back(new std::string("item" + std::to_string(i)));
}
return result;
}
- 2. Why can smart pointers effectively prevent memory leaks? What is their principle?
- 3. In what situations can memory leaks still occur even when using smart pointers?
#C++ Memory Leak, #Memory Management, #Smart Pointers, #Valgrind, #AddressSanitizer, #RAII, #Exception Safety, #Debugging Techniques
This article focuses on practical techniques for troubleshooting C++ memory leaks. It is recommended to combine memory detection tools in actual projects and develop good memory management habits, as prevention is better than cure.

Click to follow~