APP下载

C++语言内存分配研究

2014-04-29张会

计算机时代 2014年5期
关键词:编译器链表指针

张会

摘 要: 详细阐述了C++编译器的内存分配形式,给出了堆、栈、文字常量区、寄存器区、静态区、程序代码区的分配策略,分析了内存分配中易产生的问题及导致程序运行出错的原因和解决办法,从而避免程序异常和内存错误,保证程序的健壮性和正确性。

关键词: 内存; 堆; 栈; C++语言

中图分类号:TP312 文献标志码:A 文章编号:1006-8228(2014)05-44-03

Abstract: The memory allocation strategy C++ compiler is described in detail in this paper. The distribution strategy of heap, stack, literals memory, register memory in C++ language is given. The causes and the solution of memory allocation problems and program running error are analyzed to avoid exception and memory errors, and guarantee the correctness and robustness of program.

Key words: memory; heap; stack; C++ language

0 引言

C++编译器根据数据在内存中的生存期不同,将用户使用的内存分为程序区、静态存储区和动态存储区三个区域,其中动态存储区又分为堆区、栈区和寄存器区。

1 内存分配形式

C++中内存分配形式有以下六种。

1.1 栈区(stack)

栈由编译器自动分配及释放,用于存放函数参数值,局部变量值等。栈是一块连续的内存区域,它的大小为2M固定常数,因此程序中的变量能从栈中获取空间较少。若栈的剩余空间大于所申请空间,编译器将为程序提供栈空间,且按照向低地址生长方向分配连续的内存空间;若申请的空间超过栈的剩余空间,将报异常,提示栈溢出(overflow)[1]。

在调用函数时,第一个进栈的是被调用函数下一行的内存地址,再是函数参数,参数入栈的顺序自右向左,再是函数的局部变量。函数调用结束后,首先出栈的是被调函数中的局部变量,再是参数,次序是自左向右,所有变量和参数都出栈后,栈顶指针指到调用函数的下一行内存地址,程序根据该地址跳转到函数调用处的下一行自动执行。入栈数据的内存地址随着入栈顺序的先后向着内存地址减小的方向增长,随着数据不断入栈,内存地址不断变小。由于栈的先进后出原则,栈不会产生内存碎片。虽然栈内存小,但效率高,栈中存储的数据只在函数内有效,函数调用结束会因为数据出栈而被释放。

1.2 堆区(heap)

堆内存由程序员分配及释放,若程序员在程序中未释放,则在程序运行结束后,由操作系统回收。堆是不连续的空闲内存区域,各块区域由链表连接起来,其内存大小由系统中虚拟内存来确定,因此其空间较大,可以存放大量数据。

堆区分配内存空间时,系统会遍历用于记录内存空闲块的链表,首次找到一个空间大于所申请空间的堆结点时,将该结点从链表中删除。并将该结点的内存分配给程序,同时在这块内存区域首地址处记录本次内存分配大小,程序员在用delete或free释放内存时,以此识别要删除内存大小并正确删除该段内存。若申请的内存空间与堆结点上的内存空间不相等,则系统会自动将堆结点上多余内存空间回收到空闲链表中。堆区在分配内存时,链表中地址遍历方向是由低向高,因此堆区分配内存时是按照向高地址生长的方向分配不连续内存空间[1]。堆内存分配是由程序员进行分配及释放,速度较慢,易产生内存碎片。

堆是不连续的内存区域,由链表将其串接起来的空闲块,其不能像栈一样可以为其中的某个存储单元命名,堆中的每个内存单元都是匿名的,对堆的访问只能先在堆中申请内存,再把申请内存的首地址保存在一个指针中,再通过指针来访问内存。在C++中用malloc()和new关键字申请堆内存。寄存器区一般用于保存栈顶指针和指令指针。

1.4 静态区(static)

全局变量和静态变量的存储是放在一块的,已初始化的全局变量和静态变量放在一块区域,未初始化的全局变量和静态变量存放于相邻的另一块区域,内存在程序结束后由系统释放。静态变量的空间在程序编译阶段进行分配,所分配内存在程序整个运行期间都存在[2]。

1.6 程序代码区

存放函数体的二进制代码。

2 内存分配中若干问题的分析

如果对内存分配策略理解不清楚,且程序设计不当,就极易引起对运行结果异常,且难以捕获由内存分配所引起的错误。

本程序的输出结果既不是随机值,也不是3而是4,是由栈空间的连续分配所引起。在main()函数中,首先调用fun()函数,此时fun1()代码行地址入栈,接着fun()中的局部变量x入栈,fun()调用结束后把x的内存地址值返回给main()函数中的p指针,此时栈中的x已经出栈。接着在main()中调用fun1()函数,此时对fun1()中的a进行栈内存的分配,其分配的栈空间正好是fun()函数中x变量所释放的内存,对该内存赋值4后,回到主函数,此时栈空间的a变量内存被释放,而此时p指针从未改变,与此同时,程序没有其他语句代码或变量需要分配栈空间,因此p指针所指的栈空间的值未被覆盖,保留最后一次所赋值4,故程序最后输出的值是4。因此掌握了内存中存储空间的分配情况及原理,不难分析其异常的运行结果。

为防止让指向常量的指针对所指常量进行值的改变,解决的办法是把p声明成常量指针,如:const char *p;以保证不能改变所指常量的值,若试图通过指针改变常量的值,在编译检查时将报错,不致于如上述程序段发生运行时错误。

2.3 堆区内存分配

堆中的内存是匿名的,对堆内存进行访问只能通过指针进行访问。通过指针访问堆内存时,一定要注意防止内存泄露。内存泄露是指程序从堆中分配的内存块该内存释放后即存放了其他数据,但p中的地址值仍然是所释放那段内存的地址值,因此第5行输出*p的值是所释放那段内存中已存放的随机值。第6行p1指向新申请的一段内存,由于编译器会默认将释放掉的内存空间回收然后分配给新开辟的空间,因此第6行p1其实指向的是刚通p所释放掉的空间3 内存分配中存在的其他情况

内存分配发生的异常,编译器不能通过语法检查发现,通常会发生程序运行结果异常,该类错误不易被捕捉,从而给程序员检查程序错误带来不便,因此在写程序时尽量避免内存分配错误。常见的内存操作异常如下。

⑴ 内存分配失败却直接使用,如返回一个NULL指针。

⑵ 访问内存时超出了内存分配的边界。越界的内存可能保存其他变量的值,访问该内存变量的值,可能产生异常,导致程序的终止甚至崩溃[4]。

⑶ 动态申请了内存空间如链表,而未动态释放内存空间,或未完全释放,只释放了链表中的表头指针所指向结点,而未释放链表中的各个结点的内存空间,从而造成内存泄露。

(4)访问已经释放的内存,释放已经释放的内存。从而导致程序无法正确运行,得到无效值。

4 结束语

本文阐述了C++编译器的内存分配形式,提出了堆、栈、文字常量区,寄存器区的分配策略,分析了内存分配中易产生的问题和原因,同时给出了因内存分配而导致程序错误的解决办法,总之要解决程序中因内存分配所产生的问题,其前提是必须要清楚内存分配策略。通过本文对C++中内存分配策略的研究,可以使读者在以后的程序编写中有效的避免内存分配错误,从而保证程序的健壮性和正确性。

参考文献:

[1] 王文龙.C/C++数据内存分配和指针使用中若干问题的分析[J].喀什

师范学院学报,2013.34(6):36-37

[2] 韩雨涝.C语言动态内存分配研究及应用[J].计算机时代,2009.5:

33-34

[3] 王金玲,柴万东.C++动态内存分配研究[J].赤峰学院学报,2009.25

(4):19-20

[4] Eri R.Hanly.C语言详解[M].人民邮电出版社,2007.

猜你喜欢

编译器链表指针
基于相异编译器的安全计算机平台交叉编译环境设计
基于二进制链表的粗糙集属性约简
跟麦咭学编程
基于链表多分支路径树的云存储数据完整性验证机制
为什么表的指针都按照顺时针方向转动
基于改进Hough变换和BP网络的指针仪表识别
链表方式集中器抄表的设计
ARM Cortex—MO/MO+单片机的指针变量替换方法
通用NC代码编译器的设计与实现
编译器无关性编码在微控制器中的优势