C++内存管理学习笔记(2)

/****************************************************************/

/*            学习是合作和分享式的!

/* Author:Atlas                    Email:[email protected]

/*  转载请注明本文出处:

*   http://blog.csdn.net/wdzxl198/article/details/9059883

/****************************************************************/

上节内容回顾:传送门


1.C++内存管理

1.1c语言和C++内存分配

1.2区分堆、栈、静态存储区

1.3控制C++的内存分配

在C++中一种常见的问题是对内存的分配,重点是new和delete的使用不当而失控。一般来说,C++对内存的管理非常的容易和安全,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。所以频繁的使用new和delete动态分配会出现一些问题和堆破碎的风险。

所以,当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete。

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


小问:为什么需要重载operator::new和operator::delete?

虽然C++标准库已经为我们提供了new与delete操作符的标准实现,但是由于缺乏对具体对象的具体分析,系统默认提供的分配器在时间和空间两方面都存在着一些问题:分配器速度较慢,而且在分配小型对象时空间浪费比较严重,特别是在一些对效率或内存有较大限制的特殊应用中。比如说在嵌入式的系统中,由于内存限制,频繁地进行不定大小的内存动态分配很可能会引起严重问题,甚至出现堆破碎的风险;再比如在游戏设计中,效率绝对是一个必须要考虑的问题,而标准new与delete操作符的实现却存在着天生的效率缺陷。此时,我们可以求助于new与delete操作符的重载,它们给程序带来更灵活的内存分配控制。除了改善效率,重载new与delete还可能存在以下几点原因:

a)检测代码中的内存错误。

b)性能优化

b)获得内存使用的统计数据。

注意:C++中new和delete操作符不可以重载,但是在operator::new可以被重载,这个操作只是用来申请内存,不调用构造初始化对象!切记,如果需要详细阅读,请查看相关文档。

在《Effective c++》一书中也有讲解为什么需要重载,似乎重载是必须的?对于何种情况下需要重载new和delete的问题,以及如何重载的问题,需要继续研究学习。

详见文章-建议33:小心翼翼地重载operator new/ operator delete

(1)重载全局的new和delete

以下代码为《c++内存管理技术内幕》中是的,只限于简单原理学习

   1: void * operator new(size_t size)
   2: {
   3:     void *p = malloc(size);
   4:     return (p);
   5: }
   6: void operator delete(void *p)
   7: {
   8:     free(p);
   9: } 

这段代码可以代替默认的操作符来满足内存分配的请求。出于解释C++的目的,我们也可以直接调用malloc()和free()。

也可以对单个类的new 和 delete 操作符重载。这是你能灵活的控制对象的内存分配。

   1: class TestClass {
   2: public:
   3:     void * operator new(size_t size);
   4:     void operator delete(void *p);
   5:     // .. other members here ...
   6: };
   7: void *TestClass::operator new(size_t size)
   8: {
   9:     void *p = malloc(size); // Replace this with alternative allocator
  10:     return (p);
  11: }
  12: void TestClass::operator delete(void *p)
  13: {
  14:     free(p); // Replace this with alternative de-allocator
  15: }

所有TestClass 对象的内存分配都采用这段代码。更进一步,任何从TestClass 继承的类也都采用这一方式,除非它自己也重载了new 和 delete 操作符。通过重载new 和 delete 操作符的方法,你可以自由地采用不同的分配策略,从不同的内存池中分配不同的类对象。

(2)为单个类重载new[ ]和delete[ ]

必须小心对象数组的分配。你可能希望调用到被你重载过的new 和 delete 操作符,但并不如此。内存的请求被定向到全局的new[ ]和delete[ ] 操作符,而这些内存来自于系统堆。

C++将对象数组的内存分配作为一个单独的操作,而不同于单个对象的内存分配。为了改变这种方式,你同样需要重载new[ ] 和 delete[ ]操作符。

   1: class TestClass {
   2: public:
   3:     void * operator new[ ](size_t size);
   4:     void operator delete[ ](void *p);
   5:     // .. other members here ..
   6: };
   7: void *TestClass::operator new[ ](size_t size)
   8: {
   9:     void *p = malloc(size);
  10:     return (p);
  11: }
  12: void TestClass::operator delete[ ](void *p)
  13: {
  14:     free(p);
  15: }
  16: int main(void)
  17: {
  18:     TestClass *p = new TestClass[10];
  19:     // ... etc ...
  20:     delete[ ] p;
  21: } 

但是注意:对于多数C++的实现,new[]操作符中的个数参数是数组的大小加上额外的存储对象数目的一些字节。在你的内存分配机制重要考虑的这一点。你应该尽量避免分配对象数组,从而使你的内存分配策略简单。

另:对于重载new和delete或者new[ ] 和delete[ ],需要考虑诸多事宜,比如错误处理机制,继承、多态等问题。限于篇幅,将在以后的文章中详细讲解,在此买一个伏笔。

(可以参考一篇文章new、delete(new[]、delete[])操作符的重载)。

1.4 内存管理的基本要求

如果只考虑分配和释放,内存管理基本要求是“不重不漏”:既不重复 delete,也不漏掉 delete。也就说我们常说的 new/delete 要配对,“配对”不仅是个数相等,还隐含了 new 和 delete 的调用本身要匹配,不要“东家借的东西西家还”。例如:

  • 用系统默认的 malloc() 分配的内存要交给系统默认的 free() 去释放;
  • 用系统默认的 new 表达式创建的对象要交给系统默认的 delete 表达式去析构并释放;
  • 用系统默认的 new[] 表达式创建的对象要交给系统默认的 delete[] 表达式去析构并释放;
  • 用系统默认的 ::operator new() 分配的的内存要交给系统默认的 ::operator delete() 去释放;
  • 用 placement new 创建的对象要用 placement delete (为了表述方便,姑且这么说吧)去析构(其实就是直接调用析构函数);
  • 从某个内存池 A 分配的内存要还给这个内存池。

如果定制 new/delete,那么要按规矩来。见 Effective C++ 相关条款。做到以上是每个 C++ 开发人员的基本功。

1.5常见的内存错误及其对策

发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。

常见的内存错误如下:

(1)内存分配未成功,却使用了它。

  编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

(2) 内存分配虽然成功,但是尚未初始化就引用它。

  犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。 内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

(3)内存分配成功并且已经初始化,但操作越过了内存的边界。

  例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

(4)忘记了释放内存,造成内存泄露。

  含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。

(5)释放了内存却继续使用它。

  有三种情况:

  (a)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

  (b)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

  (c)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

常见的内存错误对策如下:

【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

  【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

  【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

  【规则4】动态内存的申请与释放必须配对,防止内存泄漏。

  【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。



这部分学习笔记,其中有很多可以深究的知识,比如new和delete的重载、内存错误情况及处理机制等等,学无止境。



参考文献:c++内存管理学习纲要

Atlas

Edit:2013/6/8    22:31

时间: 2024-08-03 06:01:22

C++内存管理学习笔记(2)的相关文章

C++内存管理学习笔记(6)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9120635 /************************

Linux内存管理学习笔记——内存寻址

最近开始想稍微深入一点地学习Linux内核,主要参考内容是<深入理解Linux内核>和<深入理解Linux内核架构>以及源码,经验有限,只能分析出有限的内容,看完这遍以后再更深入学习吧. 1,内存地址 逻辑地址:包含在机器语言中用来指定一个操作数或一条指令的地址. 线性地址:一个32位无符号数,用于直接映射物理地址 物理地址:片上引脚寻址级别的地址 2,逻辑地址->线性地址 2.1 段选择符与段寄存器 逻辑地址:段选择符(16位)+段内偏移(32位) index:在GDT或L

C++内存管理学习笔记(7)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9178099 /************************

C++内存管理学习笔记(5)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9112123 /************************

C++内存管理学习笔记(3)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9078965 /************************

C++内存管理学习笔记(4)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9094793 /************************

C++内存管理学习笔记(1)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /* Welcome>晓的博客:zhangxiaolong.org /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details

Linux内存管理学习笔记 转

https://yq.aliyun.com/articles/11192?spm=0.0.0.0.hq1MsD 随着要维护的服务器增多,遇到的各种稀奇古怪的问题也会增多,要想彻底解决这些“小”问题往往需要更深的Linux方面的知识.越专业.分工越细的工程师,在这方面的要求也就越高.这次,对MySQL Swap的问题的探索过程,就一不小心掉进了Linux Memory Managemant(Linux MM)的研究中去了,爬了很久才出来,这里做一个系列笔记. 笔记中很多内容都是参考<Underst

arm-linux内存管理学习笔记(1)-内存页表的硬件原理

linux kernel集中了世界顶尖程序员们的编程智慧,犹记操作系统课上老师讲操作系统的四大功能:进程调度 内存管理 设备驱动 网络.从事嵌入式软件开发工作,对设备驱动和网络接触的比较多.而进程调度和内存管理接触少之有少,更多的是敬而远之. 我的理解,想在内核开发上有更深层次的技术进步,应该对内核的内存管理进程调度等深层技术有一定的理解.不过这2块内容是内核最核心的部分,实际内核开发工作中涉及较少,很少有问题点来切入进去进行研究,网上也没有系统的资料进行讲解,学习起来谈何容易. 本着我不入地狱