C++内存管理

【摘要】在实际的工作生活中,正确的使用内存并且很好的管理这块内存是尤其重要的,在这篇博客中,我总结了最近学到的关于内存管理的常见问题以及解决办法,希望可以对你们有所帮助。

内存分配方式
  • (1)从静态存储区域分配。内存在程序编译的时候就已经分配好了(已经编址)这些内存在程序的整个运行期间都存在,如全局变量,static变量
  • (2)在堆栈上分配。在函数执行期间,函数内局部变量(包括形参)的存储单元都创建在堆栈上,函数结束时这些存储单元都会自动释放(堆栈清退)。堆栈内存分配运算内置于处理器的指令集中,效率很高,并且一般不存在失败的危险,但是分配的内存容量有限,可能出现堆栈溢出。
  • (3)从堆或者自由存储空间上分配,亦称动态内存分配。程序在运行期间用malloc()或new申请任意数量的内存,程序猿自己掌握释放内存的恰当时机(使用free()或delete)动态内存的生存期由程序猿决定,使用非常灵活,但也最容易产生问题。
一般的原则是:如果使用堆栈存储和静态存储就能满足应用要求,就不要使用动态存储。因为在堆栈上动态分配内存需要很可观的额外开销:
  • 应用程序将调用操作系统中内存管理模块的堆管理器,搜索其中是否有符合要求的连续字节内存块。特别是在经过多次动态分配后,对会出现大量的闲散内存碎片,此时需要首先将碎片合并,然后才能分配成功,在这种情况下动态分配需要很长时间
  • 如果动态分配失败,需要检查返回值或者捕获异常,这也需要额外开销。
  • 动态创建的对象可能被删除多次,甚至在删除后还会继续使用,或者根本就不会被删除,于是出现运行时错误或程序“吃”内存的对象
常见的内存错误及其对策
  • 内存分配未成功,却使用了它
  • 内存分配成功,但是尚未初始化就使用它
  • 内存分配成功并且已经初始化,但操作越过了内存的边界
  • 忘记了释放内存或者只释放了部分内存,因此造成内存泄漏。(含有这种错误的函数每调用一次就丢失一块内存)比如无意中修改了指向动态数组的指针,致使释放出错,或者使用了错误的动态数组释放语法。
  • (5)释放了内存却还在继续使用它。
  • 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象(重新设计数据结构,从根本上解决对象管理的混乱局面)
  • 函数的return语句写错了,注意不要返回指向“栈内存”的指针或者引用,因为该内存在函数结束时被自动释放
  • 使用free()或delete释放了内存后,没有将指针设置为NULL,产生野指针
  • 多次释放同一块内存
用malloc或new申请内存之后,应该立即检查指针值是否为NULL或者进行异常处理,以防止使用值为NULL的指针
不要忘记初始化指针,数组和动态内存,防止将未初始化的内存作为右值使用
避免数组或指针的下标越界,特别要当心的是++或者–操作
用free或者delete释放了内存之后,立即将指针设置为NULL,防止产生野指针

(重点)指针参数如何传内存

  • 第一种(申请了一块内存,但是指针并没有指向这块内存,导致程序执行失败)
void Getmemory(char*p,int num)
{
    p = (char*)malloc(sizeof(char)*num);  //p申请到的地址,但是这是str指针的副本,没有让str指向这块内存
}   

void Test()
{
    char* str = NULL;
    Getmemory(str,100); //str并没有申请到内存,因为它还是指向NULL
    strcpy(str,"hello");//失败了,str实际上并没有空间,无法完成拷贝操作
}
这种方法一定会造成内存泄漏,因为指针p是一个临时变量,函数结束退栈时会消亡,这个时候指针指向的内存空间就再也找不到了。这种方法吃内存很严重,一定要避免
解决办法(可以使用指针的指针或者指针的引用)
void Getmemory(char**p,int num)
{
    p = (char*)malloc(sizeof(char)*num);  //p申请到的地址,此时由于是二级指针,所以相当于直接对str指针的操作,使str指向新的内存空间
}

void Test()
{
    char* str = NULL;
    Getmemory(&str,100); //这里传的是str地址
    strcpy(str,"hello");
}
这种方法相对来说安全一点,但是不太好理解
  • 如果觉得这种操作比较麻烦的话,我们可以直接通过函数的返回值来传递动态内存
char* Getmemory(int num)
{
    char* p = (char*)malloc(sizeof(char)*num);
    return p;  //我们返回开辟空间的地址,即使指针已经消亡,但是我们得到了地址,那也就可以顺利地使用这段空间啦
}
void Test()
{
   char* str = NULL;
   str = Getmemory(100);
   strcpy(str,"hello");
   cout<<str<<endl;
   free(str);

}
这种既安全也很好理解

现在我们已经找到三种方法来解决开辟内存的问题,但是碰到这种情况,我们仍会导致开辟空间失败

char* Getmemory(void)
{
    char p[] = "hello world"; //因为字符数组是开辟在栈上的,现在返回的是栈指针,但是栈空间会在函数结束时自动清除,那么返回的指针一定指向一块无用的内存
    return p;
}
...代码同上

这种做法谈不上太大的安全问题,但是可以肯定的是就算代码可以跑过去也不是你想要的结果,一定记住不要返回栈上的指针或者引用
关于free和delete,我们需要了解的?

我们一般的看法肯定是当我们使用了free或者delete之后,它们会把指针所指向的内存空间释放掉,并且把指针也给删除,但是实际上它们并不会删除指针。这样的做法会带来野指针的问题。
- 因此,当我们使用了free之后,如果不把p设置为NULL,那就会让人误以为它是一个有效的指针
- 如果程序较长时,如果我们无法判断p指向的内存是否被释放,通常我们会用 if(p!= NULL)进行防错处理,由于p已经是野指针了,所以这个防错压根不会起作用,正确做法是在使用free之后一定记得置空。

动态内存会自动释放吗?

我们已经知道在函数体内部的局部变量在函数结束时会自动释放,那么我们在栈上动态申请一段内存空间呢?
- 事实是,指向这段空间的指针会被释放掉,但是申请的空间并不会,所以我们一定要避免出现这种情况,不然即使程序没有崩,但是当程序一直运行时,就会造成内存吃透,程序一定会崩掉。

malloc/free 和 new/delete的区别
  • malloc()与free()是C库函数,new/delete是C++运算符
  • 使用malloc()和free()没办法自动调用构造函数和析构函数,但是new/delete可以。
  • 使用new/delete更加安全,new可以自动计算对象需要的字节数量,并且也不需要显式类型转换,malloc()返回void*,必须要显式转换类型
  • 我们可以重载new/delete,但是malloc()/free()就不可以
  • 有些情况下,malloc()/free()效率要比new/delete更高
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值