C++ 内存管理实战:从 5 个经典笔试题看 new/delete 与野指针规避

📅 2026/7/13 14:33:48 👁️ 阅读次数 📝 编程学习
C++ 内存管理实战:从 5 个经典笔试题看 new/delete 与野指针规避

C++ 内存管理实战:从 5 个经典笔试题看 new/delete 与野指针规避

1. 动态内存分配基础陷阱

案例1:GetMemory函数的内存泄漏

void GetMemory(char *p) { p = (char*)malloc(100); // 问题点:修改的是局部指针副本 } void Test() { char *str = NULL; GetMemory(str); // str仍为NULL strcpy(str, "hello"); // 运行时错误 }

修正方案:二级指针传递

void GetMemory(char **p) { *p = (char*)malloc(100); if (*p == NULL) { // 良好的健壮性检查 perror("malloc failed"); exit(EXIT_FAILURE); } } void Test() { char *str = NULL; GetMemory(&str); strcpy(str, "hello"); free(str); // 必须配套释放 }

关键知识点对比表

错误类型问题表现解决方案内存示意图
值传递指针无法修改外部指针使用指针的指针[原始栈帧]->[修改副本]
未检查分配可能使用NULL指针添加NULL检查malloc失败返回NULL
忘记释放内存泄漏RAII原则已分配块未回收

2. 返回栈内存引发的野指针

案例2:局部数组返回

char *GetString() { char p[] = "hello world"; // 栈上数组 return p; // 返回局部变量地址 } void Test() { char *str = GetString(); // str成为野指针 printf(str); // 未定义行为 }

修正方案1:静态存储期

char *GetString() { static char p[] = "hello world"; // 静态存储期 return p; // 生命周期持续 }

修正方案2:动态分配

char *GetString() { char *p = (char*)malloc(20); if (p) strcpy(p, "hello world"); return p; // 调用者需负责释放 }

内存生命周期对比

  • 栈内存:函数返回即失效
  • 静态区:程序运行期间有效
  • 堆内存:需手动管理生命周期

3. delete与delete[]的误用

案例3:混合使用导致的崩溃

class MyClass { public: MyClass() { cout << "ctor" << endl; } ~MyClass() { cout << "dtor" << endl; } }; void Test() { MyClass *p1 = new MyClass[5]; // 调用5次构造函数 MyClass *p2 = new MyClass; delete p1; // 错误!只调用1次析构 delete[] p2; // 错误!错误释放方式 }

修正代码

void Test() { MyClass *p1 = new MyClass[5]; // new[]分配 MyClass *p2 = new MyClass; // new单对象 delete[] p1; // 正确释放数组 delete p2; // 正确释放单对象 }

底层机制解析

  1. new[]会在分配的内存块头部存储数组大小(通常在前4字节)
  2. delete[]根据该信息调用对应次数的析构函数
  3. 基本类型无析构函数时,delete和delete[]效果相同但不应混用

4. 多态对象的删除问题

案例4:基类非虚析构

class Base { public: ~Base() { cout << "Base dtor" << endl; } // 非虚析构 }; class Derived : public Base { int *data; public: Derived() : data(new int[100]) {} ~Derived() { delete[] data; cout << "Derived dtor" << endl; } }; void Test() { Base *p = new Derived(); delete p; // 仅调用Base析构,内存泄漏! }

修正方案:虚析构函数

class Base { public: virtual ~Base() { cout << "Base dtor" << endl; } // 虚析构 };

对象销毁过程

  1. 通过基类指针删除时,虚函数机制确保调用实际类型的析构函数
  2. 析构顺序:派生类->基类
  3. 若基类析构非虚,则只会调用基类析构,导致派生类资源泄漏

5. 野指针检测与防御编程

案例5:重复释放与悬挂指针

void Test() { int *p = new int(10); delete p; // 正确释放 *p = 20; // 使用已释放内存 delete p; // 重复释放 }

防御性编程技巧

  1. 置空策略
delete p; p = nullptr; // 确保再次访问会立即崩溃
  1. 智能指针方案
#include <memory> void SafeTest() { std::unique_ptr<int> p(new int(10)); // 自动管理生命周期 // 无需手动delete }
  1. 内存调试工具
  • Valgrind:检测内存错误
  • AddressSanitizer:实时内存检查

野指针检测表

检测方法原理优点限制
置空法释放后立即置NULL简单直接多指针指向时无效
智能指针引用计数/独占所有权自动管理学习曲线稍高
内存标记分配时添加标记可检测越界性能开销
工具检测运行时检查全面检测需要特定环境

6. 现代C++内存管理最佳实践

RAII原则应用

class ResourceGuard { int *resource; public: explicit ResourceGuard(size_t size) : resource(new int[size]) {} ~ResourceGuard() { delete[] resource; } // 禁用拷贝(C++11风格) ResourceGuard(const ResourceGuard&) = delete; ResourceGuard& operator=(const ResourceGuard&) = delete; // 支持移动(C++11) ResourceGuard(ResourceGuard&& other) noexcept : resource(other.resource) { other.resource = nullptr; } }; void ModernTest() { ResourceGuard guard(100); // 资源获取即初始化 // 无需手动释放,异常安全 }

智能指针对比

类型所有权线程安全适用场景
unique_ptr独占单一所有者资源
shared_ptr共享是(控制块)共享所有权
weak_ptr观察解决循环引用

性能优化技巧

  1. 内存池技术:
class MemoryPool { struct Block { Block* next; }; Block* freeList; public: void* allocate(size_t size) { if (!freeList) { // 批量分配策略 } void* mem = freeList; freeList = freeList->next; return mem; } };
  1. 对象池模式:
template<typename T> class ObjectPool { std::vector<std::unique_ptr<T>> pool; public: T* acquire() { if (pool.empty()) { return new T(); } auto obj = std::move(pool.back()); pool.pop_back(); return obj.release(); } };