C++内存管理

Wesley13
• 阅读 500

**C++**内存管理

一、内存分配方式

在C++中,内存分成5个区,分别是自由存储区全局/静态区常量存储区

:存放函数参数以及局部变量,在出作用域时,将自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限。

:new分配的内存块(包括数组,类实例等),需delete手动释放.如果未释放,在整个程序结束后,OS会帮你回收掉。

自由存储区:malloc分配的内存块,需free手动释放.它和堆有些相似。

全局**/**静态区:全局变量(global)和静态变量(static)存于此处。

常量存储区:常量(const)存于此处,此存储区不可修改。

堆与栈的区别

void f()

{

       int *p = new int[5];

}

上面一段代码就包含了堆与栈.指针P被分配在了栈中,而new出来的东西则被分配在了堆中,此句可以解释为”在栈中存放了一个指向堆内存的指针p”。

主要区别:

管理方式不同: 栈是编译器自动管理的,堆需手动释放

空间大小不同: 在32位OS下,堆内存可达到4GB的的空间,而栈就小得可怜。(VC6中,栈默认大小是1M,当然,你可以修改它)

能否产生碎片不同:对于栈来说,进栈/出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的new/delete,会造成内存空间的不连续,容易产生碎片。

生长方向不同:栈向下生长,以降序分配内存地址;堆向上生长,以升序分配内在地址。

分配方式不同:堆动态分配,无静态分配;栈分为静态分配和动态分配,比如局部变量的分配,就是动态分配(alloca函数),函数参数的分配就是动态分配。

分配效率不同:栈是系统提供的数据结构,计算机会在底层对栈提供支持,进栈/出栈都有专门的指令,这就决定了栈的效率比较高.堆则不然,它由C/C++函数库提供,机制复杂,堆的效率要比栈低得多。

可以看出,栈的效率要比堆高很多,所以,推荐大家尽量用栈.不过,虽然栈有如此多的好处,但远没有堆使用灵活。

二、控制C++的内存分配

其实C++的内存管理容易而且安全,因为当一个对象消除时,它的析构函数能够安全的释放所有分配的内存.在嵌入式系统中,内存的分配是一个常见问题,保守的使用内存分配是嵌入式环境中的第一原则。

当你需使用new/delete时,一个防止堆破碎的通用方法是从不同固定大小的内存池中分配不同类型的对象。对每个类重载new和delete就提供了这样的控制.

class TestClass

{

       void *operator new(size_t size);

       void operator delete(void *p);

};

void *TestClass::operator new(size_t size)

{

       void *p = malloc(size);

       return p;

}

void TestClass::operator delete(void *p)

{

       free(p);

}

而对象数组的分配又不同于单个对象的分配,所以你仍需再重载new[]和delete[]操作符.但值得注意的是,对于C++而言,分配对象数组的大小等于数组参数的大小再加上额外的对象数目的一些字节,所以要尽量避免使用对象数组。

class TestClass

{

       void *operator new[](size_t size);

       void operator delete[](void *p);

};

void *TestClass::operator new[](size_t size)

{

       void *p = malloc(size);

       return p;

}

void TestClass::operator delete[](void *p)

{

       free(p);

}

void main()

{

       TestClass *p = new TestClass[10];

       delete[] p;

}

三、常见的内存错误及对策

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

解决办法:在使用之前检查指针是否为NULL,如果指针p是函数参数,那么在函数入口处assert(p!=NULL).如果是用malloc或new申请的话,应该用if(p==NULL)进行防错处理.

2、内存分配成功**,**但未初始化就使用它

解决办法**:**不要嫌麻烦,记得初始化就行了.

3、内存分配成功且已初始化**,**但操作越过了边界

解决办法:此问题通常出现于循环之中,注意不要多1或少1就行.

4、忘记释放内存

解决办法**:**含有这个错误的函数每调用一次就丢失一块内存,造成内存耗尽.记得free或delete就行.

5、 释放了内存却继续使用它

有三种情况:
      ※程序中对象的关系过于复杂,难以搞清哪个对象是否已经释放了内存.

      ※函数中return写错,返回了指向栈中的指针或引用.

      ※ free或delete后,没有将指针设为NULL,产生”野指针”.

四、指针与数组

C++中,指针和数组有着不少相似的地方,容易让我产生错觉,以为它们是等价的,其实不然。

数组在静态存储区或是栈上被创建,数组名对应着(而不是指向)一块内存,其地址与容量在生命周期内保持

不变。而指针可以随时指向任意类型的内存块,远比数组灵活,但也危险。        

 char a[] = "hello";

        a[0] = 'x';

        char *p = "world";

        p[0] = 'y'; //试图修改常量字符串,编译器不能发现,执行会报错

杜绝野指针****”

“野指针”不是NULL指针,是指向”垃圾内存”的指针.它的缺省值是随机的,所以它会乱指一气.

产生”野指针”的原因有3种:

1、指针变量没有被初始化;

2、指针被free/delete后被没有设置为NULL;

3、指针操作超越了变量的作用域范围.如下例,p->fun()时,a已经消失。

  class A

       {

       public:

       void fun()

       {}

       };

       void Test()

       {

               A *p;

              {

                     A a;

              p = &a; //a的生命周期会在出作用域时结束

       }

       p->fun(); //p此时是"野指针"

}

五、malloc/free 和****new/delete

有了malloc/free为何还需要new/delete呢? malloc/free是标准库函数,而new/delete是运算符,它们都可用于申请/释放动态内存.但对于非基本数据类型(比如类对象)而言, malloc/free无法自动执行对象的构造/析构函数.而new/delete却可以.

malloc

函数malloc的原型: void *malloc(size_t size);

              函数malloc的使用:

              int *p = (int*)malloc(sizeof(int)*length);//length前是乘号

              可见,在使用malloc时需要进行类型转换.而使用sizeof运算符也是良好的代码风格.

new

             new内置了sizeof,所以用起来写法更简洁。

             注意,使用new创建对象数组时,只能使用对象的无参数构造函数。

             如 Obj *o = new Obj[100];

六、内存耗尽怎么办?

解决办法:

1、判断指针是否为NULL,如果是立即返回。

void fun()

{

    A *a = new A();

    if(a==NULL)

           return;

}

2、判断指针是否为NULL,如果是立即终止。

void fun()

{

    A *a = new A();

    if(a==NULL)

           exit(1);

}

提示:不要不忍心使用exit(1),否则会害死OS.所以推荐使用方法2。不过搞笑的是,在32位OS上,永远也不会内存耗尽

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这