C++内存管理 && 读高质量C++/C编程指南第7章

5 篇文章 0 订阅
4 篇文章 0 订阅

目录

内存区域划分

内存的分配方式

new / delete

比较new / delete与malloc / free

new与delete的实现原理

operator new与operator delete函数

operator new[ ]与operator delete[ ]函数

总结

常见的内存错误

1、内存尚未分配成功,却使用了它

2、相关操作导致内存越界

3、内存泄漏

4、释放内存还继续使用该内存空间

指针与数组的对比

避免野指针的出现

1、指针变量没有初始化

2、指针被释放后,未将该指针置空

3、指针操作超越了变量的作用范围


这次来重点关注一下C++中的内存管理

内存区域划分

首先来看一下C/C++中程序的内存区域划分

从下往上依次为:代码段(常量区)、数据段(静态区)、堆、栈、内核空间。以32位系统为例,总共的地址空间为2^32,即为4G的大小,其中内核空间占用1G的大小,用户空间占用3G的大小。

内存的分配方式

1、代码段也叫常量区,用来存放二进制代码常量

2、数据段也叫静态区,用来存放全局变量static变量。该区域的内存在程序编译的时候就已经分配好了,这块内存在程序于整个运行期间都存在。

3、从上分配的空间叫做动态内存分配,程序在运行的时候调用malloc或者new申请任意多少的内存,程序员自己负责free或者delete释放内存(如果不释放会造成内存泄漏)。

4、函数内部的局部变量是在上开辟的空间,函数在执行结束时这些变量会自动释放。


new / delete

在C语言中,我们用malloc 和 free来进行内存的开辟与释放,在C++中我们既可以用malloc 和 free,也可以用new 和 delete。所以为什么要引入new 和 delete?

比较new / delete与malloc / free

在内置的类型的效果是一样的

对于自定义类型效果不一样的

光用malloc 和 free 是无法满足动态对象的要求的。malloc不会调用构造函数初始化free只释放空间,不会调用析构函数。但是new开辟空间会调用构造函数初始化;delete释放空间并调用析构函数

由于C++是一种面向对象的语言,用malloc和free无法完成动态对象的内存管理,应该用new和delete。


new与delete的实现原理

operator newoperator delete函数

new和delete是用户进行动态内存申请和释放的操作符;operator new 和operator delete是系统提供的全局函数;new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

例如:

int *p=(int*)operator new(sizeof(int));

int *p = (int*)malloc(sizeof(int));

二者形式上很相似,实际上还是有区别的

malloc失败会返回一个空指针,但是operator new开辟失败会抛异常。

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。

operator delete 最终调用free函数,但是如果释放失败和free一样直接终止程序。

operator new[ ]与operator delete[ ]函数

如果我们要new出多个对象时即new T[N],底层其实调用operator new[]来实现.。同样的delete[]调用的是operator delete[]。

void* operator new[](size_t _Size);

void*operator delete[](void* _Block);

operator new[]开辟多个空间时,调用的是operator new完成N个对象的空间申请

operator delete[]释放多个空间时,调用operator delete来释放空间

总结

new的原理分两步:1、调用operator new来申请空间;2、调用对象的构造函数。

delete的原理也分两步:1、调用析构函数对对象进行析构;2、调用operator delete释放空间

new[]的原理:1、调用operator new[]开辟N个对象大小的空间申请;2、调用N次对象的构造函数

delete[]的原理:1、调用N次对象的析构函数;2、调用operator delete[]对N个对象空间进行释放


原理讲完了,现在来看看实际编程中应该注意的地方

常见的内存错误

1、内存尚未分配成功,却使用了它

建议每次申请完内存空间时,先检查指针是否为空。

2、相关操作导致内存越界

这个只能编程者自己小心了,尤其是在循环语句中很容易发生越界(例如数组)。

3、内存泄漏

动态内存的申请与释放必须匹配,无论是malloc/free还是new/delete,一个malloc对应一个free,new和delete同理。

这里还有个细节,当调用new[]一次开辟多个对象空间,在释放资源的时候只调用了delete,而未调用delete[],这样也会导致内存泄漏。前者相当于delete objects[0],只释放了一个对象的资源。

除此之外,在网络通信的过程中,socket套接字的创建但是忘记释放也会造成系统资源的泄漏。

造成内存泄漏的其他场景(20220814修改):

  • 子类继承父类时,父类的析构函数不是虚函数
  • shared_ptr出现循环引用,导致资源不得释放。
  • 多进程情况下,进程退出没有进行资源回收(wait/wait_pid)。

为了避免内存泄漏,建议:

  • 程序员自身养成良好的编程习惯。
  • 采用C++11中的智能指针来管理指针。
  • 打开的文件描述符记得释放。

4、释放内存还继续使用该内存空间

(1)程序对象调用关系过于复杂,不清楚某个对象是否已经被释放。

(2)函数返回了指向栈内存的指针或者引用,该内存出函数体后自动被销毁。

(3)使用free或者delete释放了指针,但是未将指针设为空(野指针)。


指针与数组的对比

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。

深入理解一维数组与二维数组_TangguTae的博客-CSDN博客_一维数组和二维数组https://blog.csdn.net/weixin_43164548/article/details/120647738


避免野指针的出现

野指针的出现主要有以下几种情况

1、指针变量没有初始化

任何指针变量刚被创建时不会自动成为NULL 指针,它的缺省值是随机的。所以需要进行初始化或者指向合法的内存空间。

char * p = NULL;//初始化

char * str = (char*)malloc(100*sizeof(char));//指向合法的内存

2、指针被释放后,未将该指针置空

当指针被free 或者delete 之后,没有置为NULL。

3、指针操作超越了变量的作用范围

class A
{
 public:
    void Func(void)
    {}
};

void test()
{
    A *p;
    {
        A a;
        p = &a;
    }//此时代码就有风险,因为a除了作用域
    p->Func();
}

当a出了作用域以后,p所指向的空间已经被释放,此时p就成为了野指针,这种代码非常的危险,而且还不容易察觉的到,编译器也没有报错。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值