一、前言
感谢评论,之前写的东西都好久没看过了,印象也不是很深刻,但这个问题的确问到了,反观这个文章啥也没写清楚,属实是垃圾,所以今天重新整理下,并附加一些例子进行实验;
直接抛出结果:
-
new/delete 和 new[]/delete[] 要配对使用!
记住这个原则就行; -
对于基本数据类型数组,实践证明 delete 和 delete[] 都没问题,但是使用 delete 时 Clion 会提示建议使用 delete[],通过内存泄漏检测可以发现,delete 会触发
MismatchedFree
警告,即使没有泄漏,所以建议用了 new[],就要用 delete [] 释放; -
对于对象数组,必须使用 delete[] 释放,否则会造成内存泄漏,因为 delete 只释放第一个元素的内存,同时 内存泄漏测试提示
InvalidFree
并检测到泄漏了内存; - 对于 new 出来的 string 类型的数据,释放时使用 delete 就行;
二、实验验证
2.1 实验环境
- IDEA:Clion
- C++ standard:C++14
- 内存泄漏检测工具:Valgrind
- 编译环境:Windows11(内存泄漏检测时 放在 WSL 中进行编译运行)
具体的
安装适用于 Linux 的 Windows 子系统 WSL ,完成 Clion 中对内存泄漏检测工具 Valgrind 的配置
,可以参考这篇文章:
安装适用于 Linux 的 Windows 子系统 WSL ,完成 Clion 中对内存泄漏检测工具 Valgrind 的配置,亲测可用
2.2 基本数据类型数组 delete 实验
void funcChar1(const int num) {
cout << "===== start ====" << endl;
char *p = new char[num];
cout << "===== delete p: ====" << endl;
delete[] p;
cout << "===== end ====" << endl;
}
void funcChar2(const int num) {
cout << "===== start ====" << endl;
char *p = new char[num];
cout << "===== delete p: ====" << endl;
delete p;
cout << "===== end ====" << endl;
}
void funcInt1(const int num) {
cout << "===== start ====" << endl;
int *p = new int[num];
cout << "===== delete p: ====" << endl;
delete[] p;
cout << "===== end ====" << endl;
}
void funcInt2(const int num) {
cout << "===== start ====" << endl;
int *p = new int[num];
cout << "===== delete p: ====" << endl;
delete p;
cout << "===== end ====" << endl;
}
① funcChar1 (new char[] 数组,使用 delete[] 释放)执行结果
Valgrind 面板未提示内存泄漏,说明使用正确;
② funcChar2 (new char[] 数组,使用 delete 释放)执行结果
上图可以看出,Valgrind 是提示了
MismatchedFree
警告;这个警告是说你这个没有发生内存泄漏,但是你的 delete 和 new[] 不匹配;带有 summary 的 Valgrind 会在提示下面输出 summary:
HEAP SUMMARY:
==2955== in use at exit: 0 bytes in 0 blocks
==2955== total heap usage: 1 allocs, 1 frees, 20 bytes allocated
==2955==
==2955== All heap blocks were freed -- no leaks are possible (不会发生泄漏)
同样
funcInt1
和
funcInt2
的结果跟
funcChar1
和
funcChar2
类型
2.3 对象数组 delete 实验
// cbase.h
class cbase {
public:
cbase();
~cbase();
private:
int cdata;
};
// cbase.cpp
cbase::cbase() {
cout << "constructure " << endl;
}
cbase::~cbase() {
cout << "destructor " << endl;
}
// main.cpp
void funcObject1(const int num) {
cout << "===== start ====" << endl;
cbase *p = new cbase[num];
cout << "===== delete p: ====" << endl;
delete[] p;
cout << "===== end ====" << endl;
}
void funcObject2(const int num) {
cout << "===== start ====" << endl;
cbase *p = new cbase[num];
cout << "对象大小:" << sizeof(p[0]) << endl;
cout << "===== delete p: ====" << endl;
delete p;
cout << "===== end ====" << endl;
}
int main() {
const int NUM = 3;
funcObject1(NUM);
return 0;
}
① funcObject1 (new cbase[] 数组,使用 delete[] 释放)执行结果
这里我是 new 的三个 cbase 对象,看结果,三个构造输出,三个析构输出,Valgrind 面板无内容,没毛病;
② funcObject2 ( new cbase[] 数组,使用 delete 释放)执行结果
首先,三个构造函数,只有一个析构函数,可能只释放了一个对象;
其次,对象数组的内存布局由
8字节的数组长度(不同环境可能不同,有的是4) + 各个元素占用字节数
组成;这里一共占用 20 字节,20=3*4+8;那为什么释放了一个对象却还是提示 20 字节内存泄漏呢?原因推测如下:
|8(未释放)|4(已释放)|4(未释放)|4(未释放)|
对于连续分配内存的堆而言,一个20字节的块,即使你在中间释放了 4 字节,但对于块而言,还是处于被占用的情况,所以这里 Valgrind 显示你内存泄露了 20 字节,即这个 20 字节的块 无法被其他程序 使用;
2.4 string delete 实验
void funcString1() {
cout << "===== start ====" << endl;
string *p = new string("aaa");
cout << "===== delete p: ====" << endl;
delete[] p;
cout << "===== end ====" << endl;
}
void funcString2() {
cout << "===== start ====" << endl;
string *p = new string("aaa");
cout << "===== delete p: ====" << endl;
delete p;
cout << "===== end ====" << endl;
}
① funcString1 (new string,使用 delete[] 释放)执行结果
说实话,这里我也不是很清楚为什么 一定泄露了 32 个字节,maybe:因为字符串
"aaa"
的长度为4,然后
delete[] p
把它当数组搞,释放了 4 个这样的内存,由于一个字符串 最少占 8 个字节,那么这里理所应当 非法读了 3
8=24 字节(但是 InvalidRead 又只显示 读了 2
8 =16 字节),泄露了 4*8=32 字节;有点不懂;
InvalidRead
应该就是读取了未分配内存的区域,比如这里的后面 2个 或者 3个 (8字节的)内容;
后面有机会深入下,看能不能解决疑惑;
class{
char *_Ptr; // 指向字符串的指针
int _Len; // 字符串长度
}
总而言之,这样的作法肯定是不对的;
① funcString2 (new string,使用 delete 释放)执行结果
正常的 配对着使用,就没问题;
三、总结
一个原则:
new/delete 和 new[]/delete[] 要配对使用!
,这样能在最大程度上避免内存泄漏的发生;
———-分割线:以前的文章———
今天在写C++练习题时产生一个疑惑:
new出字符数组后 需不需要在delete时加 [ ]
按道理 只要是数组应该都要加的,但是答案没有加,于是晚上回来上机运行
环境:DevC++
#include<iostream>
#include<cstring>
using namespace std;
//有一些些成员是之前用到的 不过不影响测试
class cbase
{
public:
cbase(int i,char *p_r)//构造函数
{
m_data=i;
cout<<"constructure of cbase.m_data="<<m_data<<endl;
//new出连续内存用来复制 p_r所指向的内容
p_name=new char[strlen(p_r)+1];
strcpy(p_name,p_r);
}
cbase(cbase &a)//拷贝构造函数
{
m_data=a.m_data;
p_name=new char[strlen(a.p_name)+1];
strcpy(p_name,a.p_name);
}
~cbase()//析构函数
{
delete []p_name;//第一次加上[]
//delete p_name;//第二次 不加[]
cout<<"destructor of cbase.m_data="<<m_data<<endl;
}
void get_name()
{
cout<<"p_name="<<p_name<<endl;
}
protected:
int m_data;
char *p_name;
};
int main()
{
char str[10]="weishuai";
cbase p(1,str);
p.get_name();
return 0;
}
运行结果:
第一次:加 [ ]
第二次:不加 [ ]
总结:
单单从这两次测试可以看出 对于new出来的字符数组 delete时加不加 [ ], 都能得到释放,并且系统并未给警告;
不过还有一种可能 就是不加 [ ]的话,会不会只delete了第一个字符呢?