Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!
我的博客:<但凡.
我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》
欢迎点赞,关注!
目录
1、 C语言内存管理复习
2、C++内存管理方式
3、operator new和operator delete函数(重点)
4、new和delete底层剖析
5、定位new表达式
6、new和malloc,delete和free的区别
1、 C语言内存管理复习
首先我们来先看一道题,复习一下C语言的内存管理:
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在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____ num1 在哪里?____
char2在哪里?____ *char2在哪里?___ pChar3在哪里?____
*pChar3在哪里?____ ptr1在哪里?____ *ptr1在哪里?____
我们依次来看:globalVar在主函数外定义,自然是一个全局变量,全局变量是存储在静态区的。
staticGlobalVar是一个static修饰的静态变量,同时也是全局变量,自然也是在静态区的。
staticVar是一个static修饰的静态变量,也是存储在静态区的。
localVar是一个普通的变量,他是定义在函数里的所以存储在栈区。
num1也是定义在函数内部也是存放在栈区的。
char2没什么好说的也是存放在栈区和num1一样。
*char2应该是字符'a’,他也是定义在函数中的所以存放在栈区。
pChar3和*pChar3我们一起看,其实pChar3是存放在栈区的,因为它是定义在函数中的,但是*pChar3并不是,因为他代表的是一个常量字符,应该存放在常量区。
ptr1和*ptr1一起来看,和上面的同理,ptr1也是存放在栈区的,但是这个prt1指向的内容是malloc开辟出来的,动态开辟出来的空间都是存放在堆区的。
答案:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?_C___ staticGlobalVar在哪里?__C__
staticVar在哪里?__C__ localVar在哪里?_A___ num1 在哪里?_A___
char2在哪里?__A__ *char2在哪里?_A__ pChar3在哪里?_A___
*pChar3在哪里?__D__ ptr1在哪里?__A__ *ptr1在哪里?_B___
2、C++内存管理方式
在C语言中我们是通过malloc,calloc和realloc来开辟空间的,用free来释放空间。
而在C++中,我们拥有的新的方式来开辟和释放空间,那就是new和delete。
我先写一串代码让大家看看是怎么使用的。
#include<iostream>
using namespace std;
int main()
{int* ptr1 = new int;//开辟空间int* ptr2 = new int(5);//开辟空间并复制cout << *ptr2 << endl;int* ptr3 = new int[3];//开辟多个空间*ptr3 = 9;*(ptr3 + 1) = 10;*(ptr3 + 2) = 11;cout << ptr3[0]<<" " << ptr3[1]<<" " << ptr3[2]<<" " << endl;//释放空间delete ptr1;delete ptr2;delete[] ptr3;return 0;
}
输出结果:
以上代码就是我们正常使用new和delete的代码。需要注意一点我们的new和delete是对应使用的,什么意思?就拿上面我们的代码来说我们的ptr3new出来的3个int空间,我们是放的时候应该把这3个空间都释放掉,所以得用delete[]连续释放空间。
看到这里其实new和delete根C语言里面的内存管理函数也没什么区别吗,但是其实new厉害的地方就在于他可以更加便利的对自定义类型进行操作:
#include<iostream>
using namespace std;
class A
{
public:A(int b){a = b;}~A(){cout << "~A()" << endl;}
private:int a;
};
int main()
{A* p = new A(1);delete p;return 0;
}
比如这样,我们在开辟空间的时候就可以对自定义类型的数据进行赋值。这是malloc不能够做到的。但对于内置类型的数据其实new和malloc几乎没什么区别。
3、operator new和operator delete函数(重点)
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数。
new在开辟空间的时候实际上会调用operator new,而delete在释放空间的时候会调用operator delete。
其实咱们对new和delete底层剖析一下,他们也是调用的malloc和free。
看到这大家可能会有两个问题,第一,malloc,free和operator new,operator delete有什么联系?第二,C++在设计之初为什么要让new和delete来调用malloc和free呢?带着这两个问题我们继续往下看。
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}
这是operator new的定义,我们可以看到他调用了malloc,但是他其实是对malloc还做了一些其他的封装,这不是我们了解的重点。operator delete也是这样,这里代码就不放出来了。
那么现在看第二个问题,为什么要调用malloc和free呢?
首先我们要知道malloc和new在开辟空间失败时报错的方式是不同的。malloc是直接返回一个空指针,导致这个空间无法被使用。而new是抛异常,那问题又来了,什么是抛异常呢?
在C++中,异常处理是一种在程序运行时处理错误的机制。它使用try、catch和throw关键字来实现。当程序中出现错误时,可以使用throw关键字抛出一个异常,然后在try块中捕获并使用catch块进行处理。
我们可以这么理解,异常是一种比较温柔的处理方式。在malloc开辟空间失败时会运行报错,但是异常他是不会显示出来的。 我们必须自己去捕获才知道到底是什么错误。当然了这不是重点,我们未来还会继续说这一点。
由于malloc和operator new的报错方式不一样,所以我们的operator new对malloc进行了一层封装,让他开辟失败时抛异常而不是返回空指针。
4、new和delete底层剖析
在实际new和delete自定义类型的时候,调用的顺序其实是operator new->构造函数->...处理数据...->析构函数->operator delete。
首先,new[]会在开辟内存的时候多开辟4个空间(32位),其实这4个空间存放的是一个int类型的值,这个值是表示我们连续开辟了几个该自定义类型的空间。
其实这个额外的四个字节存放的数不是给我们new使用的,他是给delete[]使用的。我们前面提到过,delete会调用析构函数,我们delete[]连续调用几个析构函数呢?delete[]是不知道的。所以就诞生了这么一个值来指示我们的delete[]应该调用几个析构函数。
但其实需要注意一点,如果我们不写这个析构函数的话,这时候释放空间的时候我们的析构函数就不知道调用几次,这时候要么报错,要么就单纯的没释放完,因为只调用了一次析构函数。
有意思的是如果我们不写析构函数,连续开辟空间,但是不用delete[]而是用delete那就没有问题,因为delete最后也是调用free,free就干脆全都释放掉了。虽然可行但是我们还是得保证使用的类型对应着,[]开辟就[]释放,不要为自己的代码埋下地雷。
5、定位new表达式
这个其实就是我们可以实现在已分配的原始内存空间中调用构造函数初始化一个对象。因为其实我们是没办法直接调用构造函数的。
//等价于new
A* p1 = (A*)operator new(sizeof(A));
//构造函数不支持显示调用
//p1->A();
//定位new/replacement new 显示调用构造函数
new(p1)A(10);//等价于delete
p1->~A();//析构函数可以显示调用
operator delete(p1);
return 0;
这个只做了解就好。
6、new和malloc,delete和free的区别
共同点:都是从堆上申请空间,并且手动维护
不同点:
1、malloc和free是函数,而new和delete是操作符。
2、malloc申请空间不能初始化而new可以初始化。
3、malloc需要手动计算字节多少,而new只需要传空间类型就好。
4、malloc返回值为void*,使用时必须强制转换,new不需要
5、malloc申请空间失败返回NULL,而new是抛异常。
6、malloc和free不会调用构造和析构函数。
好了。今天的内容就分享到这,我们下期再见!