C++内存管理学习内存泄漏(精选7篇)由网友“浪漫与算盘”投稿提供,以下文章小编为您整理的C++内存管理学习内存泄漏,供大家阅读。
篇1:C++内存管理学习内存泄漏
3 内存泄漏-Memory leak
3.1 C++中动态内存分配引发问题的解决方案
假设我们要开发一个String类,它可以方便地处理字符串数据,我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况下又不需要这么多的空间,这样是浪费了内存。很容易想到可以使用new操作符,但在类中就会出现许多意想不到的问题,本小节就以这么意外的小问题的解决来看内存泄漏这个问题。。现在,我们先来开发一个String类,但它是一个不完善的类。存在很多的问题!如果你能一下子把潜在的全找出来,ok,你是一个技术基础扎实的读者,直接看下一小节,或者也可以陪着笔者和那些找不到问题的读者一起再学习一下吧。
下面上例子,
1: /* String.h */
2: #ifndef STRING_H_
3: #define STRING_H_
4:
5: class String
6: {
7: private:
8: char * str; //存储数据
9: int len; //字符串长度
10: public:
11: String(const char * s); //构造函数
12: String; // 默认构造函数
13: ~String(); // 析构函数
14: friend ostream & operator<<(ostream & os,const String& st);
15: };
16: #endif
17:
18: /*String.cpp*/
19: #include
20: #include
21: #include “String.h”
22: using namespace std;
23: String::String(const char * s)
24: {
25: len = strlen(s);
26: str = new char[len + 1];
27: strcpy(str, s);
28: }//拷贝数据
29: String::String()
30: {
31: len =0;
32: str = new char[len+1];
33: str[0]='“0';
34: }
35: String::~String()
36: {
37: cout<<”这个字符串将被删除:“<
38: delete [] str;
39: }
40: ostream & operator<<(ostream & os, const String & st)
41: {
42: os<
43: return os;
44: }
45:
46: /*test_right.cpp*/
47: #include
48: #include
49: #include “String.h”
50: using namespace std;
51: int main()
52: {
53: String temp(“String类的不完整实现,用于后续内容讲解”);
54: cout<
55: system(”PAUSE“);
56: return 0;
57: }
运行结果(运行环境Dev-cpp)如下图所示,表面看上去程序运行很正确,达到了自己程序运行的目的,但是,不要被表面结果所迷惑!
这时如果你满足于上面程序的结果,你也就失去了c++中比较意思的一部分知识,请看下面的这个main程序,注意和上面的main加以区别,1: #include
2: #include
3: #include ”String.h“
4: using namespace std;
5:
6: void show_right(const String& a)
7: {
8: cout<
9: }
10: void show_String(const String a) //注意,参数非引用,而是按值传递。
11: {
12: cout<
13: }
14:
15: int main()
16: {
17: String test1(”第一个范例,
“);
18: String test2(”第二个范例。“);
19: String test3(”第三个范例。“);
20: String test4(”第四个范例。“);
21: cout<<”下面分别输入三个范例“<
22: cout<
23: cout<
24: cout<
25:
26: String* String1=new String(test1);
27: cout<<*String1<
28: delete String1;
29: cout<
30:
31: cout<<”使用正确的函数:“<
32: show_right(test2);
33: cout<
34: cout<<”使用错误的函数:“<
35: show_String(test2);
36: cout< 37: 38: String String2(test3); 39: cout<<”String2: “< 40: 41: String String3; 42: String3=test4; 43: cout<<”String3: “< 44: cout<<”下面,程序结束,析构函数将被调用。“< 45: 46: return 0; 47: } 运行结果(环境Dev-cpp):程序运行最后崩溃!!!到这里就看出来上面的String类存在问题了吧。(读者可以自己运行一下看看,可以换vc或者vs等等试试)
通过查看,原来主要是复制构造函数和赋值操作符的问题,读者可能会有疑问,这两个函数是什么,怎会影响程序呢。接下来笔者慢慢结识。
首先,什么是复制构造函数和赋值操作符?------>限于篇幅,详细分析请看《c++中复制控制详解(copy control)》
Tip:复制构造函数和赋值操作符
(1)复制构造函数(copy constructor)
复制构造函数(有时也称为:拷贝构造函数)是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用复制构造函数.当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数。
复制构造函数用在:
对象创建时使用其他相同类型的对象初始化;
1: Person q(”Mickey"); // constructor is used to build q.
2: Person r(p); // copy constructor is used to build r.
3: Person p = q; // copy constructor is used to initialize in declaration.
4: p = q; // Assignment operator, no constructor or copy constructor.
复制对象作为函数的参数进行值传递时;
1: f(p); // copy constructor initializes formal value parameter.
复制对象以值传递的方式从函数返回。
一般情况下,编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值。使用默认的复制构造函数是叫做浅拷贝。
相对应与浅拷贝,则有必要有深拷贝(deep copy),对于对象中动态成员,就不能仅仅简单地赋值了,而应该有重新动态分配空间。
如果对象中没有指针去动态申请内存,使用默认的复制构造函数就可以了,因为,默认的复制构造、默认的赋值操作和默认的析构函数能够完成相应的工作,不需要去重写自己的实现。否则,必须重载复制构造函数,相应的也需要重写赋值操作以及析构函数。
2.赋值操作符(The Assignment Operator)
一般而言,如果类需要复制构造函数,则也会需要重载赋值操作符。首先,了解一下重载操作符。重载操作符是一些函数,其名字为operator后跟所定义的操作符符号,因此,可以通过定义名为operator=的函数,进行重载赋值定义。操作符函数有一个返回值和一个形参表。形参表必须具有和该操作数数目相同的形参。赋值是二元运算,所以该操作符有两个形参:第一个形参对应的左操作数,第二个形参对应右操作数。
赋值和赋值一般在一起使用,可将这两个看作一个单元,如果需要其中一个,几乎也肯定需要另一个。
篇2:内存溢出和内存泄漏是什么
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。 为了便于理解我们可以有个形象的比喻就是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。
篇3:内存溢出和内存泄漏是什么
内存泄露指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 为了便于理解,我们不妨打个比方。缓冲区溢出好比是将十磅的糖放进一个只能装五磅的容器里。一旦该容器放满了,余下的部分就溢出在柜台和地板上,弄得一团糟。由于计算机程序的编写者写了一些编码,但是这些编码没有对目的区域或缓冲区——五磅的容器——做适当的检查,看它们是否够大,能否完全装入新的内容——十磅的糖,结果可能造成缓冲区溢出的产生。如果打算被放进新地方的数据不适合,溢得到处都是,该数据也会制造很多麻烦。但是,如果缓冲区仅仅溢出,这只是一个问题。到此时为止,它还没有破坏性。当糖溢出时,柜台被盖住。可以把糖擦掉或用吸尘器吸走,还柜台本来面貌。与之相对的是,当缓冲区溢出时,过剩的信息覆盖的是计算机内存中以前的内容。除非这些被覆盖的内容被保存或能够恢复,否则就会永远丢失。发生内存泄漏的程序很多,但是要想产生一定的后果,就需要这个进程是无限循环的,是个服务进程。当然,内核也是无限循环的,所以,如果内核发生了内存泄漏,情况就更加不妙。
内存泄露的问题其困难在于1.编译器不能发现这些问题。2.运行时才能捕获到这些错误,这些错误没有明显的症状,时隐时现。3.对于手机等终端开发用户来说,尤为困难。 内存泄露的解决方法:第一,良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。第二,重载 new 和 delete。这也是大家编码过程中常常使用的方法。
解决方法
关掉软件、或者重启电脑,释放一部分内存。
篇4:操作系统内存管理
操作系统内存管理:总的来说,操作系统内存管理包括物理内存管理和虚拟内存管理。这里给大家分享一些关于操作系统内存管理,希望对大家能有所帮助。
计算机的存储体系
在介绍内存管理的细节前,先要了解一下分层存储器体系:
大部分的计算机都有一个存储器层次结构,即少量的非常快速、昂贵、易变的高速缓存(cache);若干兆字节的中等速度、中等价格、易变的主存储器(RAM);数百兆或数千兆的低速、廉价、不易变的磁盘。这些资源的合理使用与否直接关系着系统的效率。
CPU缓存(Cache Memory):是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存 读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。
计算机是一种数据处理设备,它由CPU和内存以及外部设备组成。CPU负责数据处理,内存负责存储,外部设备负责数据的输入和输出,它们之间通过总线连接在一起。CPU内部主要由控制器、运算器和寄存器组成。控制器负责指令的读取和调度,运算器负责指令的运算执行,寄存器负责数据的存储,它们之间通过CPU内的总线连接在一起。每个外部设备(例如:显示器、硬盘、键盘、鼠标、网卡等等)则是由外设控制器、I/O端口、和输入输出硬件组成。外设控制器负责设备的控制和操作,I/O端口负责数据的临时存储,输入输出硬件则负责具体的输入输出,它们间也通过外部设备内的总线连接在一起。
计算存储的层次结构
当前技术没有能够提供这样的存储器,因此大部分的计算机都有一个存储器层次结构:
高速缓存(cache): 少量的非常快速、昂贵、易变的高速缓存(cache);
主存储器(RAM): 若干兆字节的中等速度、中等价格、易变的主存储器(RAM);
磁盘: 数百兆或数千兆的低速、廉价、不易变的磁盘。
这些资源的合理使用与否直接关系着系统的效率。
物理内存:连续分配存储管理方式
连续分配是指为一个用户程序分配连续的内存空间。连续分配有单一连续存储管理和分区式储管理两种方式。
3.1 单一连续存储管理
在这种管理方式中,内存被分为两个区域:系统区和用户区。应用程序装入到用户区,可使用用户区全部空间。其特点是,最简单,适用于单用户、单任务的操作系统。CP/M和 DOS 2.0以下就是采用此种方式。这种方式的最大优点就是易于管理。但也存在着一些问题和不足之处,例如对要求内存空间少的程序,造成内存浪费;程序全部装入,使得很少使用的程序部分也占用—定数量的内存。
3.2 分区式存储管理
为了支持多道程序系统和分时系统,支持多个程序并发执行,引入了分区式存储管理。分区式存储管理是把内存分为一些大小相等或不等的分区,操作系统占用其中一个分区,其余的分区由应用程序使用,每个应用程序占用一个或几个分区。分区式存储管理虽然可以支持并发,但难以进行内存分区的共享。
分区式存储管理引人了两个新的问题:内碎片和外碎片。
内碎片是占用分区内未被利用的空间,外碎片是占用分区之间难以利用的空闲分区(通常是小空闲分区)。
为实现分区式存储管理,操作系统应维护的数据结构为分区表或分区链表。表中各表项一般包括每个分区的起始地址、大小及状态(是否已分配)。
分区式存储管理常采用的一项技术就是内存紧缩(compaction)。
篇5:DOS内存管理
在DOS下,系统中存在以下四种内存: 常规内存(ConventionalMemory); 高端内存(UpperMemory); 扩充内存(ExpandedMemory); 扩展内存(ExtendedMemory),
常规内存指的是0-640K的内存区。在DOS下,一般的应用程序只能使用系统的常规内存,因而都要受到640Kb内存的限制。而且由于DOS本身和config.sys文件中的安装的设备驱动程序和autoexec.bat文件中执行的内存驻留程序都要占用一些常规内存,所以应用程序能使用的常规内存是不到640K的。有很多时候,我们都要想方设法地整理内存,好为一些“胃口”比较大的应用程序留出足够的常规内存,这一点想必是许多DOS时代的电脑爱好者最熟悉不过的了。
高端内存是指位于常规内存之上的384K内存。程序一般不能使用这个内存区域,但是EMM386.exe可以激活高端内存的一部分,并且它允许用户将某些设备驱动程序和用户程序用Devicehigh或LH(即loadhigh)装入高端内存。dos=high,umb也是把DOS的一部分装到高端内存里,
这里的umb是高端内存块(UpperMemoryblock)的缩写。
扩充内存是一种早期的增加内存的标准,最多可扩充到32M。使用扩充内存必须在计算机中安装专门的扩充内存板,而且还要安装管理扩充内存板的管理程序。由于扩充内存是在扩展内存之前推出的,所以大多数程序都被设计成能使用扩充内存,而不能使用扩展内存。由于扩充内存使用起来比较麻烦,所以在扩展内存出现后不久就被淘汰了。
扩展内存只能用在80286或更高档次的机器上,目前几乎所有使用DOS的机器上超过1M的内存都是扩展内存。扩展内存同样不能被DOS直接使用,DOS5.0以后提供了Himem.sys这个扩展内存管理程序,我们可以通过它来管理扩展内存。emm386.exe可以把扩展内存(XMS)仿真成扩充内存(EMS),以满足一些要求使用扩充内存的程序。
最后再强调一下,不管扩充内存或扩展内存有多大,DOS的应用程序只能在常规内存下运行。有的程序可以通过DOS扩展器(比如DOS4GW.exe等程序)使CpU进入保护模式,从而直接访问扩展内存;但是要注意,进入保护模式以后,计算机就脱离了DOS状态。
篇6:glibc内存管理
X86平台LINUX进程内存布局如下:
上面个段的含义如下:
text:存放程序代码的,编译时确定,只读;
data:存放程序运行时就能确定的数据,可读可写;
bss:定义而没有初始化的全局变量和静态变量;
heap:一般由程序员分配,如果不释放的话在程序结束的时候可能被OS回收;
stack:有编译器自动分配释放,存放函数的参数、局部变量等;
Mmap:映射区域;
程序可以直接使用系统调用来管理heap和mmap,但更多的时候是使用C提供的malloc和free来动态地分配和释放内存,Linux上的stack的限制大致是8M,而在Windows上为2M.
C风格的内存管理程序:
也就是malloc和free,主要是通过brk或者mmap添加额外的虚拟内存。对于那些需要保持长期存储的程序使用malloc来管理内存可能会非常令人失望,如果有大量的不固定的内存引用,经常难以知道他们如何被释放。
池式内存管理:
应用程序可以更简单地管理内存;内存分配和回收更快;可以预先分配错误处理池,以便程序在常规内存被耗尽时仍然可以恢复;有非常易于使用的标准实现。
内存池只适用于操作可以分阶段的程序;通常不能和第三方库很好滴合作;如果程序的结构发生变化则不得不修改内存池;必须记住需要从哪个池进行分配。
引用计数:
不能忘记调用引用计数函数;无法释放作为循环数据结构的一部分;在多线程环境中更难也更慢。
垃圾回收:
永远不必担心内存的双重释放或者对象的生命周期;
无法干涉何时释放内存;比其他形式的内才能管理更慢;如果忘记将不再使用的指针设置为null,
内存管理器的设计目标:
最大化兼容性;
最大化可移植性(能很好地和OS交流);
浪费最小的空间(管理自身的数据结构也是需要内存的,还有一个需要注意的是碎片);
最快的速度(2/8原则,主要用来优化热点);
最大化可调性(能适应多种分配的需求,或者是通过配置来适应);
最大化局部性(这里考虑的是CPU的cache和内存之间的关系);
最大化调试功能(作为程序员就不用说了);
最大化适应性(在不修改配置时候的适应性);
ptmalloc实现了malloc和free以及一组其他函数,以提供动态内存管理的支持。分配器处于用户程序和内核之间,用来响应用户的分配请求,向操作系统申请内存,然后将其返回给用户程序。为了保持高效的分配,分配器一般都会预先分配一大块内存,并通过某种算法管理。用户释放掉的内存也并不是立即就返回给操作系统。ptmalloc在设计的时候折中了高效性、高空间利用率、高可用性等设计目标。其设计假设如下:
用mmap来分配长生命周期的大内存;
特别大的内存分配总是使用mmap;
短生命周期的内存分配使用brk;
尽量只缓存小的、临时使用的内存,而大的内存则直接归还给系统;
小内存块只有在malloc和free的时候进行合并;
收缩堆的条件是当前free的快的大小加上前后能合并的chunk的大小大于64K,并且堆顶的大小达到阀值;
需要长期存储的程序不适合ptmalloc;
总体上的结构如下:
而实际存在数据的是Chunk,使用中的Chunk的结构如下:
其中:
P:表示前一个Chunk是否在使用中;
M:标志Chunk是从那个内存区域获得的虚拟内存;
A:标志是否是主分配区;
空闲的Chunk在内存中的结构如下:
在glibc中使用bin来管理空闲的chunk,细节就不说了,
当空闲的chunk被链接到bin中的时候,ptmalloc会检查他前后的chunk是否也是空闲的,如果是的话,就会合并成一个大的chunk.bin的结构如下:
ptmalloc为了提高分配的速度,会把一些小的chunk先放到Fast bins中。fast bins中的chunk并不改变它的使用标志P,这样也就无法将他们合并,当用户分配小的内存的时候,ptmalloc首先会在fast bins中查找响应的空闲块,然后再去查找Unsorted bins中空闲的chunk.被合并后的chunk、或者是不能放在fast bins中的chunk会首先放在Unsorted bin中,如果在分配的时候在Unsorted bin中无法满足要求,则将Unsorted bin中的chunk加入到bins中。
因为在分配内存的时候是用低地址到高地址分配的,这样一个分配到的大的内存(用来模拟sub-heap)的上面很有可能是有一块空闲的内存,也就是Top chunk.Top chunk是在fast bin和bins之后才考虑的,所以这段区间并不在bins这些结构中。如果ptmalloc设法在top chunk中分配一段空间时且top chunk不够大,这时会重新分配一个新的sub-heap,并将top chunk迁移到新的sub-heap上。新的sub-heap与已有的sub-heap用单向链表连接起来。如下:
当要分配的空间足够大的时候,ptmalloc会使用mmap来直接使用内存映射来讲页映射到进程空间。这样分配的chunk在被free时将直接接触映射,再次对这样的内存区域的引用将会引起段错误。
Last remainder chunk是另外一种特殊的chunk,在分配一个small chunk的时候,如果在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需的small chunk大小。那么它将会分裂成两个,一个供用户使用,另一个变成了新的Last remainder chunk.
ptmalloc的响应用户内存分配请求的具体步骤:
获取主分配区的锁,如果失败就查找非主分配区,再不行就创建新的非主分配区;
将用户请求的大小转换为实际需要分配的chunk空间的大小;
如果chunk_size<=max_fast则转4,否则,跳转5;
尝试在fast bins中分配,如果成功则结束返回;
如果chunk_size<=512B则下一步,否则跳转6;
查找对应的small bins,如果找到则分配成功;否则转7;
合并fast bins中的chunk,遍历unsorted bin中的chunk,如果只有一个chunk,并且这个chunk在上次的分配中被使用过,并且所需分配的chunk大小属于smallbins,并且chunk的大小满足要求,则直接将该chunk进行切割,分配结束;否则将其放入bins;
在large bins中查找;如果失败转9;
如果top chunk满足要求,则从中分配出一块;否则转10;
如果是主分配区,调用sbrk增加top chunk的大小;否则分配一个新的sub-heap;或者直接使用mmap来分配;如果需要用mmap分配转11,否则转12;
使用mmap系统调用为程序的内存空间映射一块空间,然后将指针返回给用户程序;
判断是否第一次调用malloc,若是主分配区,则进行一次初始化工作。否则根据10的规则来分配。
为了避免Glibc内存暴增,使用时需要注意一下几点:
后分配的内存先释放(top chunk的考虑);
不适合用于管理长生命周期的内存,特别是持续不定期分配和释放长生命周期的内存;
不要关闭ptmalloc的mmap分配阀值动态调整机制;
多线程分阶段执行的程序不适合使用ptmalloc(更适合使用内存池);
尽量减少程序的线程数量和避免频繁分配、释放内存;
篇7:c内存泄露的原因
简单的说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
(1). 常发性内存泄漏。
发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
(2). 偶发性内存泄漏。
发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
(3). 一次性内存泄漏。
发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
(4). 隐式内存泄漏。
程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
★ c 面试题
★ 青岛朗讯笔试题目
★ 电气工程师面试题
【C++内存管理学习内存泄漏(精选7篇)】相关文章:
嵌入式系统低功耗软件技术分析论文2022-11-04
经典Java基础面试题2024-02-06
阿里巴巴面试题java2022-05-06
c语言面试题2023-09-01
java开发面试题型与技巧2023-12-21
富士康Java开发面试题目2024-01-29
基础JAVA笔试题2022-04-29
UCTD系统及其关键技术介绍2022-06-26
java基础测试题带答案2023-01-04
检讨书?c2022-05-08