C++ Memory Management and Memory Leaks
C++ provides powerful memory management capabilities, but also requires developers to have a clear understanding of memory lifecycles. Improper memory management can lead to serious issues such as memory leaks, dangling pointers, and double frees.
C++ Memory Areas
1. Stack
- Stores local variables, function parameters, return addresses
- Automatically allocated and released
- Limited size (typically a few MB)
- Fast allocation
2. Heap
- Dynamically allocated memory
- Manually managed (new/delete) or managed by smart pointers
- Size limited by available system memory
- Slower allocation
3. Global/Static Area
- Stores global variables, static variables
- Allocated at program start, released at program end
- Lifetime spans the entire program
4. Constant Area
- Stores string constants, const variables
- Read-only, cannot be modified
5. Code Area
- Stores program binary code
- Read-only
Causes of Memory Leaks
1. Forgetting to free memory
cppvoid leakExample() { int* ptr = new int(42); // Forgot to delete ptr; }
2. Exception causes skip of free
cppvoid leakWithException() { int* ptr = new int(42); someFunctionThatThrows(); // If it throws, ptr won't be freed delete ptr; }
3. Circular references
cppclass A { public: std::shared_ptr<B> b; }; class B { public: std::shared_ptr<A> a; // Circular reference causes memory leak }; auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b = b; b->a = a;
4. Improper pointer assignment
cppvoid lostPointer() { int* ptr = new int(42); ptr = new int(100); // First allocated memory leaks delete ptr; }
Detecting Memory Leaks
1. Valgrind (Linux)
bashvalgrind --leak-check=full --show-leak-kinds=all ./your_program
2. AddressSanitizer (ASan)
bashg++ -fsanitize=address -g your_program.cpp -o your_program ./your_program
3. Visual Studio Debugger
- Use CRT debug heap
- Add at the beginning of code:
cpp#define _CRTDBG_MAP_ALLOC #include <crtdbg.h> _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
4. Custom memory tracking
cppclass MemoryTracker { private: static std::unordered_map<void*, size_t> allocations; public: static void* allocate(size_t size) { void* ptr = malloc(size); allocations[ptr] = size; return ptr; } static void deallocate(void* ptr) { if (allocations.erase(ptr) == 0) { std::cerr << "Double free or invalid pointer: " << ptr << std::endl; } free(ptr); } static void reportLeaks() { if (!allocations.empty()) { std::cerr << "Memory leaks detected:" << std::endl; for (const auto& [ptr, size] : allocations) { std::cerr << " Leaked " << size << " bytes at " << ptr << std::endl; } } } };
Preventing Memory Leaks
1. Use smart pointers
cpp// Not recommended void badExample() { Resource* res = new Resource(); // Easy to forget delete } // Recommended void goodExample() { auto res = std::make_unique<Resource>(); // Automatically released }
2. Follow RAII principles
cppclass FileHandler { private: FILE* file; public: FileHandler(const char* filename) { file = fopen(filename, "r"); if (!file) { throw std::runtime_error("Failed to open file"); } } ~FileHandler() { if (file) { fclose(file); } } // Disable copy FileHandler(const FileHandler&) = delete; FileHandler& operator=(const FileHandler&) = delete; };
3. Use standard containers
cpp// Not recommended void badContainer() { int* arr = new int[100]; // Process array delete[] arr; } // Recommended void goodContainer() { std::vector<int> arr(100); // Automatically manages memory }
4. Handle exceptions correctly
cpp// Not recommended void badException() { int* ptr = new int(42); riskyOperation(); // May throw exception delete ptr; } // Recommended void goodException() { auto ptr = std::make_unique<int>(42); riskyOperation(); // Even if it throws, ptr will be properly released }
Memory Alignment
Why memory alignment is needed:
- CPUs access aligned memory faster
- Some architectures require specific types to be aligned
- Avoid performance degradation or program crashes
Alignment methods:
cpp// Use alignas to specify alignment struct alignas(16) AlignedStruct { int a; double b; }; // Use alignof to query alignment requirements std::cout << "Alignment: " << alignof(AlignedStruct) << std::endl; // Use aligned_alloc to allocate aligned memory void* ptr = aligned_alloc(16, 1024);
Memory Pool
Advantages of memory pool:
- Reduces memory fragmentation
- Improves allocation/deallocation speed
- Reduces system call frequency
Simple implementation:
cpptemplate <typename T, size_t BlockSize = 1024> class MemoryPool { private: struct Block { T data; Block* next; }; Block* freeList; std::vector<std::unique_ptr<Block[]>> blocks; public: MemoryPool() : freeList(nullptr) { allocateBlock(); } ~MemoryPool() = default; T* allocate() { if (!freeList) { allocateBlock(); } Block* block = freeList; freeList = freeList->next; return &block->data; } void deallocate(T* ptr) { Block* block = reinterpret_cast<Block*>(ptr); block->next = freeList; freeList = block; } private: void allocateBlock() { auto newBlock = std::make_unique<Block[]>(BlockSize); for (size_t i = 0; i < BlockSize - 1; ++i) { newBlock[i].next = &newBlock[i + 1]; } newBlock[BlockSize - 1].next = nullptr; freeList = &newBlock[0]; blocks.push_back(std::move(newBlock)); } };
Best Practices
1. Prefer stack memory
cpp// Prefer void stackPreferred() { int value = 42; process(value); } // Use heap only when necessary void heapWhenNeeded() { auto value = std::make_unique<int>(42); process(*value); }
2. Use standard library containers
cppstd::vector<int> vec; // Automatically manages memory std::string str; // Automatically manages memory std::map<int, int> map; // Automatically manages memory
3. Avoid raw pointers
cpp// Not recommended int* ptr = new int(42); // ... use ptr delete ptr; // Recommended auto ptr = std::make_unique<int>(42); // ... use ptr // Automatically released
4. Use const correctness
cppvoid process(const std::vector<int>& data); // Avoid copying void modify(std::vector<int>& data); // Clearly indicates modification
5. Regularly perform memory analysis
bash# Use Valgrind to detect memory leaks valgrind --leak-check=full --show-leak-kinds=all ./your_program # Use AddressSanitizer g++ -fsanitize=address -g your_program.cpp -o your_program ./your_program
Common Errors
1. Double free
cppint* ptr = new int(42); delete ptr; delete ptr; // Undefined behavior
2. Freeing unallocated memory
cppint* ptr; delete ptr; // Undefined behavior
3. Mismatched array new/delete
cppint* arr = new int[10]; delete arr; // Wrong, should use delete[] arr
4. Using delete on stack memory
cppint value = 42; int* ptr = &value; delete ptr; // Wrong, cannot free stack memory
5. Dangling pointer
cppint* ptr = new int(42); delete ptr; *ptr = 100; // Undefined behavior, dangling pointer