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

发布时间:2026/7/13 14:29:58

C++ 内存管理实战:从 5 个经典笔试题看 new/delete 与野指针规避 C 内存管理实战从 5 个经典笔试题看 new/delete 与野指针规避1. 动态内存分配基础陷阱案例1GetMemory函数的内存泄漏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; // 正确释放单对象 }底层机制解析new[]会在分配的内存块头部存储数组大小通常在前4字节delete[]根据该信息调用对应次数的析构函数基本类型无析构函数时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; } // 虚析构 };对象销毁过程通过基类指针删除时虚函数机制确保调用实际类型的析构函数析构顺序派生类-基类若基类析构非虚则只会调用基类析构导致派生类资源泄漏5. 野指针检测与防御编程案例5重复释放与悬挂指针void Test() { int *p new int(10); delete p; // 正确释放 *p 20; // 使用已释放内存 delete p; // 重复释放 }防御性编程技巧置空策略delete p; p nullptr; // 确保再次访问会立即崩溃智能指针方案#include memory void SafeTest() { std::unique_ptrint p(new int(10)); // 自动管理生命周期 // 无需手动delete }内存调试工具Valgrind检测内存错误AddressSanitizer实时内存检查野指针检测表检测方法原理优点限制置空法释放后立即置NULL简单直接多指针指向时无效智能指针引用计数/独占所有权自动管理学习曲线稍高内存标记分配时添加标记可检测越界性能开销工具检测运行时检查全面检测需要特定环境6. 现代C内存管理最佳实践RAII原则应用class ResourceGuard { int *resource; public: explicit ResourceGuard(size_t size) : resource(new int[size]) {} ~ResourceGuard() { delete[] resource; } // 禁用拷贝C11风格 ResourceGuard(const ResourceGuard) delete; ResourceGuard operator(const ResourceGuard) delete; // 支持移动C11 ResourceGuard(ResourceGuard other) noexcept : resource(other.resource) { other.resource nullptr; } }; void ModernTest() { ResourceGuard guard(100); // 资源获取即初始化 // 无需手动释放异常安全 }智能指针对比类型所有权线程安全适用场景unique_ptr独占否单一所有者资源shared_ptr共享是控制块共享所有权weak_ptr观察是解决循环引用性能优化技巧内存池技术class MemoryPool { struct Block { Block* next; }; Block* freeList; public: void* allocate(size_t size) { if (!freeList) { // 批量分配策略 } void* mem freeList; freeList freeList-next; return mem; } };对象池模式templatetypename T class ObjectPool { std::vectorstd::unique_ptrT pool; public: T* acquire() { if (pool.empty()) { return new T(); } auto obj std::move(pool.back()); pool.pop_back(); return obj.release(); } };

相关新闻