前言
1.内存分布
接下来看一段代码,分别区分这些东西在内存的哪个区
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
-
选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?C 全局变量,静态区
staticGlobalVar在哪里?__C__静态变量,静态区
staticVar在哪里?C
localVar在哪里?__A__函数里面正常的变量,那就在栈区
num1 在哪里?___A_函数里面正常的数组,那就在栈区
char2在哪里?A 函数里面正常的数组,那就在栈区,这个实际上是在栈上创建了一个数组,数组里面的值就是这些
char2在哪里?___所以char2指的是a也在栈上,char2这个数组名的存储也在栈上,指向abcd
pChar3在哪里?____ pchar3是函数里面的变量,所以在栈上 ,指向静态区的字符串
pChar3在哪里?____而“abcd”这是一个静态的字符串,不可修改的,所以pchar3就指向静态区的a,所以前面还要加上一个const,不然权限就缩小了
ptr1在哪里?____ 栈
*ptr1在哪里?____堆
2.C++内存管理方式
C语言中动态内存管理方式:malloc/calloc/realloc/free
而在C++中就是new 和 delete了
这两个不是函数,是操作符
我们先介绍这两个东西的用法
int main()
{
int* p1 = new int;
delete p1;
return 0;
}
int* p1 = new int;这句话表示开辟一个int大小的空间,p1来指向
delete p1;这句话就表示释放开辟的那段空间了
int main()
{
int* p1 = new int(1);
cout << (*p1) << endl;
delete p1;
return 0;
}
开辟的时候还可以初始化
int main()
{
int* p1 = new int[10];
delete[] p1;
return 0;
}
这个就表示开辟10个int大小的空间,然后对应释放空间要用delete[]
这个就表示为数组了
int main()
{
int* p1 = new int[10] {1,2,3,4,5};
for (int i = 0; i < 10; i++)
{
cout << p1[i] << ' ';
}
delete[] p1;
return 0;
}
在创建的时候的后面加上{}就表示是对数组的初始化了,数组后面没写的默认为0
class A
{
public:
A(int val = 1)
{
cout << "A(int val=1)" << endl;
}
A(int a, int b)
{
cout << "A(int a, int b)" << endl;
}
private:
int _val;
};
int main()
{
A* p = new A;
delete p;
return 0;
}
相应的,自定义类型也可以用new来创建,而且创建的时候会调用构造函数,delete释放的时候会调用析构函数,new创建时,如果不传参就会调用默认构造函数
class A
{
public:
A(int val = 1)
{
cout << "A(int val=1)" << endl;
}
A(int a, int b)
{
cout << "A(int a, int b)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _val;
};
int main()
{
A* p = new A(1);
A* p1 = new A[10]{ 1,2,3,4,{2,3} };
delete p;
delete[] p1;
return 0;
}
对于数组的释放一定要用delete[],不然很容易出错
数组的赋值也是这样的,赋的什么值就对应去哪个构造函数,没有赋值的话就去调用默认函数
数组的每个赋值就相当于是这样的
p1[0]=1,p2[1]=2,而p1[0]又是一个自定义类型,所以这里相当于单参数的构造函数的隐式类型转换
{2,3}就相当于多参数的隐式类型转换
因为new和delete会自动调用构造函数和析构函数,而malloc和free就不行所以以后尽量用new和delelte
3.operator new与operator delete函数
其实在new操作符里会调用operator new这个函数,这是一个函数,不是运算符重载
operator delete也是一样的道理,delete操作符里会调用operator delete这个函数
int main()
{
int* p = (int*)operator new(sizeof(int));
*p = 1;
cout << (*p) << endl;
operator delete(p);
return 0;
}
operator new的使用与malloc一模一样
operator delete的使用与free一模一样
因为operator delete是free的分装,就是在free的基础上加了一些东西
operator new也是malloc的分装,在原先malloc的基础上加了一些东西
加了什么东西呢,加的是抛异常,这是个什么东西呢,这个我先不讲,先留着,后面会说明的,反正这个东西就和以前C语言的错误码是比较类似的东西,是针对错误进行了说明,这个有什么用呢
以前用malloc开辟空间,每开辟一个都要做判断,看是否开辟失败,开辟n次,你就要判断n次,就要写这段代码n次,但是有了抛异常,你就只需要写这个代码一次就可以了,这就是它的作用
所以总结来说
new=operator new+构造函数=malloc+抛异常+构造函数
delete=operator delete+析构函数=free+抛异常+析构函数
而同样的
delete[]也是=operator delete[]+析构函数
而operator delete[]的底层就是delete[]
这就是为什么delete要调用析构函数和new要调用构造函数的原因,因为如果只有operator delete 的话,如果类里面的变量还要指向一段空间的话,那么是无法释放这段空间的,只有析构函数才可以
4.new和delete的实现原理
在讲这个之前我们先来看很多的代码和其结果
int main()
{
int* p = new int;
free(p);
return 0;
}
这段代码没有问题
int main()
{
int* p = new int[10];
free(p);
return 0;
}
int main()
{
int* p = new int[10];
delete p;
return 0;
}
这段代码也没有问题
所以对于内置类型,不管是malloc还是new,不管是free(和operator delelte是一样的效果,所以我只说一个)还是delelte,随便你怎么混合使用,都不会有什么问题,甚至对于数组只使用delete,不使用delete[]都没有问题,所以内置类型随你怎么用,开辟有释放就可以了,不必new对应delete
class A
{
public:
A(int val = 1)
{
cout << "A(int val=1)" << endl;
}
A(int a, int b)
{
cout << "A(int a, int b)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _val;
};
int main()
{
A* p = new A;
free(p);
return 0;
}
对于自定义类型的单个对象可以这样混着用,没问题
class A
{
public:
A(int val = 1)
{
cout << "A(int val=1)" << endl;
}
A(int a, int b)
{
cout << "A(int a, int b)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _val;
};
int main()
{
A* p = new A[10];
free(p);
return 0;
}
在这里也是终于出问题了,对于自定义类型的数组,单单只对它free程序会崩溃的
int main()
{
A* p = new A[10];
delete p;
return 0;
}
当然如果改成delete,改成operator delete还是一样的结果
只有适合它的delelte[]是没有问题的,这到底是为什么呢
我们把开辟时的代码转到反汇编看看,还发现了这里实际开辟了44个字节空间,为什么呢,
好了,现在开始解答疑惑
对于自定义类型的数组,编译器会在前面多开辟一个int类型的空间,因为这是为了记录后面要析构多少次,不然只释放不析构还是不行的,因为要先析构,所以要有个数据来记录析构多少次,然后再释放,开辟的时候是这样的,开辟44字节,然后p还指向了中间点的位置,不是最开始的位置,在delete[]开始释放的时候,回先去访问前面的10,再从10开始释放就可以了
所以说这就是为什么只用free只用delete释放是不可以的了,是会崩溃的,因为他俩只会从p指向位置开始释放,不会向前走一步在释放,而从开辟空间的非开头位置开始释放就是会崩溃,所以就是这个原因
class A
{
public:
A(int val = 1)
{
cout << "A(int val=1)" << endl;
}
A(int a, int b)
{
cout << "A(int a, int b)" << endl;
}
//~A()
//{
// cout << "~A()" << endl;
//}
private:
int _val;
};
int main()
{
A* p = new A[10];
delete p;
return 0;
}
但是如果这样的话,就不会崩溃了,为什么呢
那我先问为什么会在前面开辟空间来记录呢?还不是因为,你自己手动实现了析构函数,这就说明了析构很重要,析构是必须得,不析构可能就会内存泄漏
如果没有显示实现析构函数,就说明了析构不重要,不析构也没事,不析构也不会内存泄漏,所以就不析构了,所以就不单独开辟空间来记录了,所以就没事了
5.定位new表达式
接下来我们不用new,delete来实现一下new和delete的功能
class A
{
public:
A(int val = 1)
{
cout << "A(int val=1)" << endl;
}
A(int a, int b)
{
cout << "A(int a, int b)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _val;
};
int main()
{
A* p = new A;
p->~A();
operator delete(p);
return 0;
}
p->~A();
operator delete(p);
这个就是对delete的实现了
但是要实现new的话没那么简单,因为构造函数不能显示调用,只能在创建对象的时候用
所以这里就提供了其他的方法
int main()
{
A* p = (A*)operator new (sizeof(A));
new(p)A;
p->~A();
operator delete(p);
return 0;
}
这个调用的是默认构造
int main()
{
A* p = (A*)operator new (sizeof(A));
new(p)A(10);
p->~A();
operator delete(p);
return 0;
}
int main()
{
A* p = (A*)operator new (sizeof(A));
new(p)A(10,2);
p->~A();
operator delete(p);
return 0;
}
这两个都是构造函数,所以这就实现了new
但是这样有什么用呢,直接使用new不好吗,这里的用处就是在内存池的时候有用
所谓内存池就是在栈中拿来一部分空间,然后这样分开使用,就可以调用内存池的空间,就不调用栈的空间了,因为new默认调用的空间是栈中的,所以要访问内存池就要用上面这个方法了,具体什么方法,还有内存池怎么实现,现在都不讲
内存池的功能主要是提高效率的,这就相当于家长给了你生活费你自己用,没有内存池就相当于每次花钱都要找家长要,你没有生活费,那这样效率就很低
6. malloc/free和new/delete的区别
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
如果是多个对象,[]中指定对象个数即可 - malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常 - 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
总结
下一篇博客我们来学习一些模版