动态内存管理

 C语言和c++都对内存进行了划分,本质上是由操作系统划分的。划分的这部分内存叫做进程虚拟地址空间,本质上是虚拟的地址。还要通过一系列操作和物理地址相关联。

数据段是系统的叫法,语言上的叫法叫静态区。

代码段是系统的叫法,语言上的叫法叫常量区。代码段的代码不是程序员写的代码,而是代码编译好后执行的二进制指令。CPU从这里取指令来执行,有寄存器控制执行的位置,寄存器会记录执行到哪一条指令。作为语言上的常量区,它负责储存一些常量。比如字符串常量。

一个可执行程序(文件,在磁盘中)包含全局数据/静态数据会在静态区开辟好,编译好的指令会被加载到常量区,栈区/堆区的数据是在运行时才开辟。一个程序运行起来就会在内存中开辟一个进程虚拟地址空间。这个空间就是给程序用的。从操作系统角度,每一个程序运行起来都是进程。

C语言是通过malloc,calloc,realloc,free来进行动态内存管理的,c++兼容了C语言的语法,但是c++也有c++的动态内存管理方式:new,delete。(new和delete是操作符)说明c++的方式肯定有存在的必要。new和delete是为了解决C语言动态内存管理中一些不好用的地方。

new的简单用法:

int main()

{

    int* p1 = new int;//new1个对象

    int* p2 = new int[10];//new10个对象,c++98[]没有初始化方式,c++11才出现初始化方式

    int* p3 = new int(10);//new1个对象,并初始化为10

    int* p4 = new int[10]{10, 1, 2}

        //c++11支持的语法,new10个对象,并将第1,2,3个数初始化为10,1,2。

    delete p1;//new出来的要delete

    delete[] p2;

        //new带[]出来的,delete也要加上[]。要匹配,不匹配可能会报错,也可能崩溃,也可能没有影响。

    delete p3;

    delete[] p4;

    return 0;

}

对内置类型而言,malloc和new除了用法不同,没有什么太大区别。有区别的地方在自定义类型。

对C语言来说,开辟一个自定义空间需要:

struct ListNode

{

    int _val;

    struct ListNode* _next;

};

struct ListNode* buyListNode (int x)

{

    struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));

    assert(node);

    node->_val = x;

    node->_next = NULL;

    return node;

}

int main()

{

    struct ListNode* n1 = buyListNode(1);

    return 0;

}

而对c++来说

struct ListNode

{

    int _val;

    ListNode* _next;

    

    ListNode(int x = 0)

        :_val(x)

        ,_next(nullptr)

    {}

};

int main()

{

    ListNode* n1 = new ListNode(1);

    return 0;

}

对自定义类型来说,C语言malloc开辟空间不初始化,c++new开辟空间并调用构造函数初始化。而且malloc失败后返回0,new失败后抛异常。所以new不需要检查。

struct构造的类和class构造的类相比,更适用于每个节点都可以被访问的情况。比如带头双向循环列表,这个数据结构要求每个节点都能被访问到。

struct ListNode

{

    int _val;

    ListNode* _next;

    ListNode* _prev;

    

    ListNode(int x = 0)

        :_val(x)

        ,_prev(nullptr)

        ,_next(nullptr)

    {}

};

class List

//head节点是链表类的私有,在链表类中可以可以访问,要取出数据,通过成员函数接口实现。

{

public:

    List()

    {

        _head = new ListNode;//new和malloc的最大区别:会调用自定义类型的构造函数进行初始化

        _head->next = _head;

        _head->prev = _head;

    }

private:

    ListNode* _head;

};

int main()

{

    ListNode* n1 = new List;

    delete n1;

    //delete和free不同的地方在于会调用类的析构函数,即:如果存在自定义类型的成员变量,除了会销毁n1指向的空间,还会调用自定义类型的析构函数

    return 0;

}

new开辟失败会抛异常:

框内部分的意思是:抛异常,没有捕获。

捕获的方式——先了解一下,异常的机制涉及继承和多态

将可能抛异常的地方包含在try中

捕获后运行的结果

operator new和operator delete函数

operator new和operator delete是库函数,但不是函数重载,偏偏有operator,这是c++上一个不合理的点。

 框出的部分是抛异常的

这是operator new的源码,由图中可知,operator new实际上是由malloc封装得到的。malloc失败,抛异常。

operator delete的源码。delete是由_free_dbg封装的,_free_dbg就是free函数,free函数在C语言上是一个宏函数。

operator new和malloc在功能上是一样的,operator new返回的是void*,所以也需要强转。

执行上面的代码,发现在operator new中没有调用构造函数。在使用方面,malloc和operator new是一样的。

operator new和malloc的区别在于,operator new失败后会抛异常。

和new相比,operator new没有优势,所以operator new并不是我们使用的,而是在new的底层原理中使用的。

Stack* n1 = new Stack;

这行代码转换成指令:开辟空间,调用构造函数,即call malloc,call Stack构造函数。

malloc失败会返回空,但是不满足c++的需求,返回 错误码(值)的方式不怎么舒服,用抛异常的方式更合适。

(面向对象语言的库中都有一个要求:不再用C语言返回 值的方式来处理)

所以call malloc就变成了call operator new

operator new与operator delete的类专属重载(目前只需要了解)

在某些特定的情况下会用到。比如频繁插入链表节点,new要调用operator new,operator new要调用malloc。频繁的new频繁的调用。解决方法是:不向堆申请空间。

有个东西叫池化技术。就是自己建立一个池,比如今后会学到的内存池,进程池。然后向池申请空间。从池中释放空间。

可以理解为向池申请空间相对于向内存申请空间更快。可以提高效率。

但是c++new对象必然会调用构造函数,也就必然会调用new,new会调用库中的operator new。

c++存在一种机制,叫重载专属operator new。

在成员函数中添加:

构造函数中,new就会调用成员函数中的operator new,而不是库中的operator new。operator new中的内容就是自己决定的了。就可以在里面写内存池的机制。如何建立内存池现在不好讲。

new和delete的实现原理

5.1 内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

5.2 自定义类型

new的原理

    1. 调用operator new函数申请空间

    2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

    1. 在空间上执行析构函数,完成对象中资源的清理工作

    2. 调用operator delete函数释放对象的空间

new T[N]的原理

    1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请

    2. 在申请的空间上执行N次构造函数

delete[]的原理

    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理

    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

定位new(了解)

定位new表达式(placement-new) 

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

Stack* obj = (Stack*)operator new(sizeof(Stack));

//obj已经开辟,Stack成员为私有,构造函数不能显式调用。这时使用定位new就可以显式调用构造函数完成初始化

new(obj)Stack;//可以传参,new(obj)Stack(4);

这个写法等价于:Stack* obj = new Stack(4);

malloc/free和new/delete的区别——在用法上和底层上有区别

用法上:

1. malloc和free是函数,new和delete是操作符

2. malloc申请的空间不会初始化,new可以初始化

3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可

4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

底层上:

5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常

6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值