1.关于动态申请内存的问题 出现率極高
程序的全局变量存在于(静态存储区)中程序动态申请的数据存在于(堆)中
毛病出在函数GetMemory 中。编译器总是要为函数的每个参数制作临时副本指针参数p的副本是 _p,编译器使 _p = p如果函数体内的程序修改了_p嘚内容,就导致参数p的内容作相应的修改这就是指针可以用作输出参数的原因。在本例中_p申请了新的内存,只是把 _p所指的内存地址改變了但是p丝毫未变。所以函数GetMemory并不能输出任何东西事实上,每执行一次GetMemory就会泄露一块内存因为没有用free释放内存。
请问运行Test函数会有什么样的结果?
不要用return语句返回指向“栈内存”的指针因为该内存在函数结束时自动消亡;
函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的因为GetString2内的“hello world”是常量字符串,位于静态存储区它在程序生命期内恒定不變。无论什么时候调用GetString2它返回的始终是同一个“只读”的内存块。
free(p); // p 所指的内存被释放但是p所指的地址仍然不变2.指针形参&引用形参
当函數需要处理数组且函数体不依赖于数组的长度时应使用指针形参,其他情况下应使用引用形参:
优点:可以明确地表示函数所操纵的是指向數据元素的指针而不是数组本身,而且可以使用任意长度的实参数组来调用函数;
缺点:函数体不能依赖于数组的长度否则容易造成數据内存的越界访问,从而产生错误的结果或者程序崩溃
优点:在函数体中依赖数组的长度是安全的;
缺点:限制了可以传递实参数组,只能使用长度匹配的实参数据来调用函数
一、先来谈谈在C语言下,动态内存分配和释放的特点
在内存的动态存储区中分配一块长度為"size" 字节的连续区域。
如果分配成功则返回所分配内存空间的首地址,否则返回NULL申请的内存不会进行初始化。
“类型说明符”表示把该區域用于何种数据类型(类型说明符*)表示把返回值强制转换为该类型指针。例如: pc=(char *) malloc (100);
按照所给的数据个数和数据类型所占字节数分配一个 num * size 連续的空间。
函数返回该存储区的起始地址
calloc申请内存空间后,会自动初始化内存空间为 0但是malloc不会进行初始化,其内存空间存储的是一些随机数据
动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr把ptr内存空间调整为size。
申请的内存空间不会进行初始化
作鼡:释放由上面3种函数所申请的内存空间。
参数:ptr:指向需要释放的内存空间的首地址
函数原型为 void free(void *ptr)其中ptr为存放待释放空间起始地址的指针變量,函数无返回值应注意:ptr所指向的空间必须是前述函数所开辟的。
例如free((void *)p1);将上例开辟的16个字节释放可简写为free(p1);由系统自动进行类型转換。
二、C++语言动态内存分配
在C++中内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
申请和释放堆中分配嘚存储空间,分别使用new 和 new deletee 的两个运算符来完成:
指针变量名 = new 类型名(初始化式);
malloc与free是C++/C语言的标准库函数new/new deletee是C++的运算符。它们都可以用于申请動态内存和释放内存
对于非内部数据类型对象而言,光用malloc/free无法满足动态对象的要求对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数由于malloc/free是库函数而不是运算符,不在编译器控制权限之内不能够把执行构造函数和析构函数的任务强加于malloc/free.
2)当不需要再使用申请的内存时记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它
3)这两个函数應该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)
4)虽然malloc()函数的类型是(void *),任何类型的指针都鈳以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查
答案是从堆里面获得空间。也就是说函数返回嘚指针是指向堆里面的一块内存操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时就会遍历该链表,然后就尋找第一个空间大于所申请空间的堆结点然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序
如果不提供显示初始化,对于类类型用该类的默认构造函数初始化;而内置类型的对象则无初始化。
也可以对动态创建的对象做值初始化:
string *ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型没有必要对其对象进行值初始化)
2) 对于非内部数据类型的对象而言光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数对象在消亡の前要自动执行析构函数。由于malloc/free是库函数而不是运算符不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符new deletee注意new/new deletee不是库函数。
我们不要企图用malloc/free来完成动态对象的内存管理应该用new/new deletee。由于内部数据类型的“对象”没有构造与析构的过程对它们而言malloc/free和new/new deletee是等价的。
如果用free释放“new创建的动态对象”那么该对象因无法执行析构函数而可能导致程序出错。如果用new deletee释放“malloc申请的动态内存”结果也会导致程序出错,泹是该程序的可读性很差所以new/new deletee必须配对使用,malloc/free也一样
而 malloc 则必须要由我们计算字节数并且在返回后强行转换为实际类型的指针。
代码也能通过编译但事实上只分配了1个字节大小的內存空间,当你往里头存入一个整数就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容被改写
下面一段话的原理讲的比较清晰
答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存操莋系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆結点然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序就是这样!
说到这里,不得不另外插入一个小话题相信夶家也知道是什么话题了。什么是堆说到堆,又忍不住说到了栈!什么是栈下面就另外开个小部分专门而又简单地说一下这个题外话:
堆是大家共有的空间,分全局堆和局部堆全局堆就是所有没有分配的空间,局部堆就是用户分配的空间堆在操作系统对进程 初始化嘚时候分配,运行过程中也可以向系统要额外的堆但是记得用完了要还给操作系统,要不然就是内存泄漏
什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的栈在线程开始的时候初始化,每个线程的栈互相独立每个函数都有自己的栈,栈被用来在函数之間传递参数操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器栈空间不需要在高级语言里面显式的分配和释放。
以上的概念描述是标准的描述不过有个别语句被我删除,不知道因为这样而变得不标准了^_^.
通过上面对概念的描述可以知道:
栈是由编译器自动汾配释放,存放函数的参数值、局部变量的值等操作方式类似于数据结构中的栈。
堆一般由程序员分配释放若不释放,程序结束时可能由OS回收注意这里说是可能,并非一定所以我想再强调一次,记得要释放!
注意它与数据结构中的堆是两回事分配方式倒是类似于鏈表。(这点我上面稍微提过)
所以举个例子,如果你在函数上面定义了一个指针变量然后在这个函数里申请了一块内存让指针指向咜。实际上这个指针的地址是在栈上,但是它所指向的内容却是在堆上面的!这一点要注意!所以再想想,在一个函数里申请了空间後比如说下面这个函数:
就这个例子,千万不要认为函数返回函数所在的栈被销毁指针也跟着销毁,申请的内存也就一样跟着销毁了!这绝对是错误的!因为申请的内存在堆上而函数所在的栈被销毁跟堆完全没有啥关系。所以还是那句话:记得释放!
这个问题比较简单,其实我是想和第二大部分的题目相呼应而已!哈哈!free()释放的是指针指向的内存!注意!释放的是内存不是指针!这点非常非常重要!指针是一个变量,只有程序结束时才被销毁释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾是未定义的,所以说是垃圾因此,前面我已经说过了释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了非常重要啊这一点!
好了!这个“题外话”终于说完了。就这么简单说一次知道个大概就可以了!下面就进入第三个部分:
这個部分我今天才有了新的认识!而且是转折性的认识!所以,这部分可能会有更多一些认识上的错误!不对的地方请大家帮忙指出!
事实仩仔细看一下free()的函数原型,也许也会发现似乎很神奇free()函数非常简单,只有一个参数只要把指向申请空间的指针传递
给free()中的参数就可鉯完成释放工作!这里要追踪到malloc()的申请问题了。申请的时候实际上占用的内存要比申请的大因为超出的空间是用来记录对这块内存的管悝信息。先看一下在《UNIX环境高级编程》中第七章的一段话:
大多数实现所分配的存储空间比所要求的要稍大一些额外的空间用来记录管悝信息——分配块的长度,指向下一个分配块的指针等等这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息这种類型的错误是灾难性的,但是因为这种错误不会很快就暴露出来所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息
以上这段话已经给了我们一些信息了。malloc()申请的空间实际我觉得就是分了两个不同性质的空间一个就是用来记录管理信息的空間,另外一个就是可用空间了而用来记录管理信息的实际上是一个结构体。在C语言中用结构体来记录同一个对象的不同信息是
天经地義的事!下面看看这个结构体的原型:
对于size,这个是实际空间大小。这里其实我有个疑问is_available是否是一个标记?因为我看了free()的源代码之后对这個变量感觉有点纳闷(源代码在下面分析)这里还请大家指出!
所以,free()就是根据这个结构体的信息来释放malloc()申请的空间!而结构体的两个荿员的大小我想应该是操作系统的事了但是这里有一个问题,malloc()申请空间后返回一个指针应该是指向第二种空间也就是可用空间!不然,如果指向管理信息空间的话写入的内容和结构体的类型有可能不一致,或者会把管理信息屏蔽掉那就没法释放内存空间了,所以会發生错误!(感觉自己这里说的是废话)
好了!下面看看free()的源代码我自己分析了一下,觉得比起malloc()的源代码倒是容易简单很多只是有个疑问,下面指出!
看一下函数第二句这句非常重要和关键。其实这句就是把指向可用空间的指针倒回去让它指向管理信息的那块空间,因为这里是在值上减去了一个结构体的大小!后面那一句free->is_available = 1;我有点纳闷!我的想法是:这里is_available应该只是一个标记而已!因为从这个变量的名稱上来看is_available 翻译过来就是“是可以用”。不要说我土!我觉得变量名字可以反映一个变量的作用特别是严谨的代码。这是源代码所以峩觉得绝对是严谨的!!这个变量的值是1,表明是可以用的空间!只是这里我想了想如果把它改为0或者是其他值不知道会发生什么事?!但是有一点我可以肯定就是释放绝对不会那么顺利进行!因为这是一个标记!
当然,这里可能还是有人会有疑问为什么这样就可以釋放呢?我刚才也有这个疑问。后来我想到释放是操作系统的事,那么就free()这个源代码来看什么也没有释放,对吧但是它确实是确萣了管理信息的那块内存的内容。所以free()只是记录了一些信息,然后告诉操作系统那块内存可以去释放具体怎么告诉操作系统的我不清楚,但我觉得这个已经超出了我这篇文章的讨论范围了
那么,我之前有个错误的认识就是认为指向那块内存的指针不管移到那块内存Φ的哪个位置都可以释放那块内存!但是,这是大错特错!释放是不可以释放一部分的!首先这点应该要明白而且,从free()的源代码看ptr只能指向可用空间的首地址,不然减去结构体大小之后一定不是指向管理信息空间的首地址。所以要确保指针指向可用空间的首地址!鈈信吗?自己可以写一个程序然后移动指向可用空间的指针看程序会有会崩!
最后可能想到malloc()的源代码看看malloc()到底是怎么分配空间的,这里媔涉及到很多其他方面的知识!有兴趣的朋友可以自己去下载源代码去看看
1、malloc和free是C语言标准函数库中的两个函数,new/new deletee是C++语言中两个运算符
3、new 不止是分配内存,而且会调用类的构造函数同理new deletee会调用类的析构函数,而malloc则只分配内存不会进行初始化类成员的工作,同样free 也不會调用析构函数
4、malloc得到的指针无类型,new出来的指针是带有类型信息的
5、对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求对象在创建的同时 要自动执行构造函数,对象在消亡之前要自动执行析构函数由于malloc/free是库函数而不是运算符,不在编译器控制权限之內不能够把执行构造函数和析构函数的任务强加于malloc/free。
6、如果用free释放“new创建的动态对象”那么该对象因无法执行析构函数而可能导致程序出错。如 果用new deletee释放“malloc申请的动态内存”理论上讲程序不会出错,但是该程序的可读性很差所以new/new deletee 必须配对使用,malloc/free也一样
8、如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错如 果用new deletee释放“malloc申请的动态内存”,理论上讲程序不会出错但是该程序的可读性很差。所以new/new deletee 必须配对使用malloc/free也一样。
[2]在栈上创建。在执行函数时函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放栈内存分配运算内置于处理器的指令集中,效率很高但是分配的内存容量有限。
[3]从堆上分配亦称动态內存分配。程序在运行的时候用malloc或new申请任意多少的内存程序员自己负责在何时用free或new deletee释放内存。动态内存的生存期由程序员决定使用非瑺灵活,但如果在堆上分配了空间就有责任回收它,否则运行的程序会出现内存泄漏频繁地分配和释放不同大小的堆空间将会产生堆內碎块。
一个程序将操作系统分配给其运行的内存块分为4个区域如下图所示。
一个由C/C++编译的程序占用的内存分为以下几个部分,
1、栈区(stack) 由编译器自动分配释放 存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。其操作方式类似于数据结构中的栈
2、堆区(heap) 一般由程序员分配释放, 若程序员不释放程序结束时可能由OS回收 。分配方式类似于链表
3、全局区(静态区)(static)存放全局變量、静态数据、常量。程序结束后有系统释放
4、文字常量区 常量字符串就是放在这里的 程序结束后由系统释放。
5、程序代码区存放函數体(类成员函数和全局函数)的二进制代码
stack: 由系统自动分配 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
但是注意p1、p2本身是在栈中的。
3.2申请后系统的响應
栈:只要栈的剩余空间大于所申请空间系统将为程序提供内存,否则将报异常提示栈溢出
堆:首先应该知道操作系统有一个记录空閑内存地址的链表,当系统收到程序的申请时会遍历该链表,寻找第一个空间大于所申请空间的堆结点然后将该结点从空闲结点链表Φ删除,并将该结点的空间分配给程序
对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小这样,代码中的new deletee语句才能正确的释放本内存空间
由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
堆:堆是向高地址扩展的数据结构,是不连续的内存区域这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的洏链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存由此可见,堆获得的空间比较灵活也比较大。
棧由系统自动分配速度较快。但程序员是无法控制的
堆是由new分配的内存,一般速度比较慢而且容易产生内存碎片,不过用起来最方便。
另外在WINDOWS下,最好的方式是用VirtualAlloc分配内存他不是在堆,也不是栈而是直接在进程的地址空间中保留一快内存,虽然用起来最不方便泹是速度快,也最灵活
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址然後是函数的各个参数,在大多数的C编译器中参数是由右往左入栈的,然后是函数中的局部变量注意静态变量是不入栈的。
当本次函数調用结束后局部变量先出栈,然后是参数最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小堆中的具体内容有程序员安排。
a是在运行时刻赋值的;而b是在编译时就确定的;但是茬以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快 比如:
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二種则要先把指针值读到edx中再根据edx读取字符,显然慢了
堆和栈的主要区别由以下几点:
3、能否产生碎片不同;
管理方式:对于栈来讲,昰由编译器自动管理无需我们手工控制;对于堆来说,释放工作由程序员控制容易产生memory leak。
空间大小:一般来讲在32位系统下堆内存可鉯达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的但是对于栈来讲,一般都是有一定的空间大小的例如,在VC6下面默认的棧空间大小是1M。当然这个值可以修改。
碎片问题:对于堆来讲频繁的new/new deletee势必会造成内存空间的不连续,从而造成大量的碎片使程序效率降低。对于栈来讲则不会存在这个问题,因为栈是先进后出的队列他们是如此的一一对应,以至于永远都不可能有一个内存块从栈Φ间弹出在他弹出之前,在他上面的后进的栈内容已经被弹出详细的可以参考数据结构。
生长方向:对于堆来讲生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲它的生长方向是向下的,是向着内存地址减小的方向增长
分配方式:堆都是动态分配嘚,没有静态分配的堆栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的比如局部变量的分配。动态分配由malloca函数进行汾配但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放无需我们手工实现。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活有时候汾配大量的内存空间,还是用堆好一些
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界)因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构产生以想不到的结果。
从C++角度上说使用new分配堆空间可以调用类的构造函数,而malloc()函数仅仅是一個函数调用它不会调用构造函数,它所接受的参数是一个unsigned long类型同样,new deletee在释放堆空间之前会调用析构函数而free函数则不会。
从结果可以看出使用new/new deletee可以调用对象的构造函数与析构函数,并且示例中调用的是一个非默认构造函数但在堆上分配对象数组时,只能调用默认构慥函数不能调用其他任何构造函数。
内存管理是C++最令人切齿痛恨的问題也是C++最有争议的问题,C++高手从中获得了更好的性能更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨但内存管理在C++中無处不在,内存泄漏几乎在每个C++程序中都会发生因此要想成为C++高手,内存管理一关是必须要过的除非放弃C++,转到Java或者.NET他们的内存管悝基本是自动的,当然你也放弃了自由和对内存的支配权还放弃了C++超绝的性能。本期专题将从内存管理、内存泄漏、内存回收这三个方媔来探讨C++内存管理问题
程序员们经常编写内存管理程序,往往提心吊胆如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们躲是躲不了的。本文的内容比一般教科书的要深入得多读者需细心阅读,做到真正地通晓内存管理