【c++面向对象编程】第28篇:new/delete vs malloc/free:C++中正确动态内存管理

发布时间:2026/5/19 6:54:14

【c++面向对象编程】第28篇:new/delete vs malloc/free:C++中正确动态内存管理 目录一、一个会崩溃的程序二、new/delete 与 malloc/free 的对比正确用法示例三、new 和 delete 的工作原理new 做了两件事delete 做了两件事四、new[] 和 delete[]数组的配对为什么不能混用五、完整例子正确与错误的对比六、placement new在已分配内存上构造对象七、常见错误总结错误1混用 malloc/free 和 new/delete错误2忘记 delete 导致内存泄漏错误3重复 delete错误4delete 后继续使用指针八、最佳实践建议九、这一篇的收获一、一个会崩溃的程序先看这段代码猜猜会发生什么cpp#include iostream #include cstdlib #include string using namespace std; class Student { private: string name; int age; public: Student(const string n, int a) : name(n), age(a) { cout 构造函数: name endl; } ~Student() { cout 析构函数: name endl; } void speak() { cout 我是 name , age 岁 endl; } }; int main() { // 错误示范1malloc 分配对象不调用构造函数 Student* s1 (Student*)malloc(sizeof(Student)); s1-speak(); // 未定义行为name 和 age 未初始化 // 错误示范2new 分配的对象用 free 释放 Student* s2 new Student(张三, 20); free(s2); // 未定义行为没有调用析构函数 return 0; }运行结果可能输出乱码、崩溃或者看起来“正常”但内存泄漏。问题malloc只分配原始内存不调用构造函数 → 对象处于未初始化状态free只释放内存不调用析构函数 →string的内部资源泄漏二、new/delete 与 malloc/free 的对比特性malloc/freenew/delete头文件cstdlib不需要C 关键字返回值void*需要强转类型化指针无需强转内存大小手动计算sizeof编译器自动计算构造函数❌ 不调用✅ 调用析构函数❌ 不调用✅ 调用失败时返回NULL抛出bad_alloc异常配对要求必须malloc/free配对必须new/delete配对正确用法示例cpp// ✅ 正确new/delete 配对 Student* s1 new Student(李四, 21); delete s1; // ✅ 正确malloc/free 配对用于 POD 类型尚可但不推荐 int* arr (int*)malloc(10 * sizeof(int)); free(arr); // ❌ 错误混用 Student* s2 (Student*)malloc(sizeof(Student)); delete s2; // 未定义行为 // ❌ 错误混用 Student* s3 new Student(王五, 22); free(s3); // 未定义行为三、new 和 delete 的工作原理new 做了两件事分配内存类似malloc调用构造函数在分配的内存上构造对象cppStudent* s new Student(赵六, 23); // 编译器大致转换成 // 1. void* mem operator new(sizeof(Student)); // 分配内存 // 2. Student* s new(mem) Student(赵六, 23); // placement new 调用构造delete 做了两件事调用析构函数释放内存类似freecppdelete s; // 编译器大致转换成 // 1. s-~Student(); // 调用析构 // 2. operator delete(s); // 释放内存四、new[] 和 delete[]数组的配对为数组分配内存时必须使用new[]和delete[]配对。cpp// ✅ 正确new[] / delete[] 配对 Student* arr new Student[3] { {A, 1}, {B, 2}, {C, 3} }; delete[] arr; // 调用 3 次析构函数 // ❌ 错误new[] 配 delete只调用一次析构 Student* arr2 new Student[3]; delete arr2; // 未定义行为只析构第一个元素且释放内存错误为什么不能混用new[]分配时会在内存块开头通常是前 4-8 字节存储数组元素个数以便delete[]知道要调用多少次析构函数。cpp// new[] 分配的内存布局示意 // [元素个数][元素0][元素1][元素2]... // 4字节 N字节 N字节 N字节如果用delete而不是delete[]编译器以为只有一个对象只调用一次析构函数 → 剩余对象的资源泄漏释放内存时地址计算错误 → 崩溃五、完整例子正确与错误的对比cpp#include iostream #include cstring using namespace std; class StringHolder { private: char* data; size_t len; public: StringHolder(const char* s) { len strlen(s); data new char[len 1]; strcpy(data, s); cout 构造: data (分配内存) endl; } ~StringHolder() { cout 析构: data (释放内存) endl; delete[] data; } void print() const { cout 内容: data endl; } }; int main() { cout 正确示范new/delete endl; StringHolder* s1 new StringHolder(Hello); s1-print(); delete s1; // ✅ 调用析构释放内存 cout \n 正确示范new[]/delete[] endl; StringHolder* arr new StringHolder[2] { {First}, {Second} }; arr[0].print(); arr[1].print(); delete[] arr; // ✅ 调用两次析构 cout \n 错误示范new[] 配 delete endl; StringHolder* arr2 new StringHolder[2] { {A}, {B} }; delete arr2; // ❌ 只调用一次析构第二个对象内存泄漏 // 程序可能崩溃或者看似正常但内存泄漏 return 0; }输出正确部分text 正确示范new/delete 构造: Hello (分配内存) 内容: Hello 析构: Hello (释放内存) 正确示范new[]/delete[] 构造: First (分配内存) 构造: Second (分配内存) 内容: First 内容: Second 析构: Second (释放内存) 析构: First (释放内存)错误示范部分的行为是未定义的。六、placement new在已分配内存上构造对象有一种特殊情况placement new允许在已有的内存上构造对象不分配新内存。cpp#include iostream #include new // placement new 需要这个头文件 using namespace std; class Point { public: int x, y; Point(int a, int b) : x(a), y(b) { cout Point 构造: ( x , y ) endl; } ~Point() { cout Point 析构 endl; } }; int main() { // 分配原始内存不构造对象 void* mem operator new(sizeof(Point)); cout 分配了原始内存地址: mem endl; // 在已有内存上构造对象 Point* p new(mem) Point(10, 20); // 使用对象 cout p-x p-x , p-y p-y endl; // 手动调用析构 p-~Point(); // 释放原始内存 operator delete(mem); return 0; }使用场景内存池实现预分配一大块内存重复使用共享内存中的对象构造高性能场景避免重复分配/释放七、常见错误总结错误1混用 malloc/free 和 new/deletecpp// ❌ 各种混用都是未定义行为 Student* s1 (Student*)malloc(sizeof(Student)); delete s1; Student* s2 new Student(A, 1); free(s2); Student* s3 new Student[10]; delete s3; // 应该是 delete[]错误2忘记 delete 导致内存泄漏cppvoid leak() { Student* s new Student(临时, 0); // 忘记 delete s; → 内存泄漏 }错误3重复 deletecppStudent* s new Student(A, 1); delete s; delete s; // ❌ 未定义行为double free错误4delete 后继续使用指针cppStudent* s new Student(A, 1); delete s; s-speak(); // ❌ 悬空指针未定义行为 // 应该 s nullptr;八、最佳实践建议永远配对使用new配deletenew[]配delete[]malloc配free优先使用智能指针unique_ptr、shared_ptr→ 后续章节会讲delete 后立即置空delete p; p nullptr;防止二次释放避免手动 new/delete能用vector、string就别自己管理内存类管理资源时遵循五法则析构、拷贝构造、拷贝赋值、移动构造、移动赋值九、这一篇的收获你现在应该理解new调构造函数 分配内存delete调析构函数 释放内存malloc/free只处理内存不处理构造/析构必须配对使用new配deletenew[]配delete[]混用是未定义行为可能崩溃或资源泄漏placement new在已有内存上构造对象用于内存池等高级场景 小作业写一个Timer类构造函数中记录开始时间析构函数中输出“对象存活了 X 毫秒”。用new创建对象故意忘记delete观察析构函数是否被调用。然后用delete正确释放对比行为。下一篇预告第29篇《定位newplacement new在指定内存上构造对象》——深入 placement new 的用法结合内存池技术展示如何手动控制对象的构造位置和生命周期。

相关新闻