《深入理解C语言的内存管理.docx》由会员分享,可在线阅读,更多相关《深入理解C语言的内存管理.docx(17页珍藏版)》请在第一文库网上搜索。
1、深入理解C语言的内存管理之前在学Java的时候对于Java虚拟机中的内存分布有一定的了解,但是最近在看一些C,发现居然自己对于C语言的内存分配了解的太少。问题不能拖,我这就来学习一下吧,争取一次搞定。在任何程序设计环境及语言中,内存管理都十分重要。内存管理的基本概念分析C语言内存的分布先从1inux下可执行的C程序入手。现在有一个简单的C源程序he11o.c1 #inc1ude2 #inc1ude3 intvar1=1;4 intmain(void)5 intvar2=2;6 rintf(he11ozwor1d!n);7 exit(0);9经过gcche11o.c进行编译之后得到了名为a.ou
2、t的可执行文件tuhooo1oca1host1eet_code$Is-a1a.out-rwxrwr-x.1tuhoootuhooo8592Ju12220:40a.outIs命令是查看文件的元数据信息tuhooo1oca1host1eet_code$fi1ea.outa.out:E1F64-bit1SBexecutab1e,86-64zversion1(SYSV)zdynamica11y1inked(usesshared1ibs),forGNU/1inux2.6.32,Bui1dIDsha1=23c58f2cad39d8b15b91f0cc8129055833372afeznotstripped
3、fi1e命令用来识别文件类型,也可用来辨别一些文件的编码格式。它是通过查看文件的头部信息来获取文件类型,而不是像Windows通过扩展名来确定文件类型的。tuhooo1oca1host1eet_code$sizea.outtextdatabssdechexfi1ename(代码区静态数(全局初始化静态数(未初始化数据(十进制总(十六制总(文件据)据)区)和)和)名)13015608186974da.out显示一个目标文件或者链接库文件中的目标文件的各个段的大小,当没有输入文件名时,默认为a.out。size:支持的目标:e1f32-i386a.out-i386-1inuxefi-app-ia3
4、2e1f32-1itt1ee1f32-bigsrecSymbo1srectekhexbinaryihextrad-coreo那啥,可执行文件在存储(也就是还没有载入到内存中)的时候,分为:代码区、数据区和未初始化数据区3个部分。进一步解读(1)代码区(textsegment)o存放CPU执行的机器指令(machineinstructions)o通常,代码区是可共享的(即另外的执行程序可以调用它),因为对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。(2)全局初始化数据区/静态数据区(i
5、nitia1izeddatasegment/datasegment)o该区包含了在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。例如,一个不在任何函数内的声明(全局数据):1intmaxcount=99;使得变量maxcount根据其初始值被存储到初始化数据区中。1staticmincount=100;这声明了一个静态数据,如果是在任何函数体外声明,则表示其为一个全局静态变量,如果在函数体内(局部),则表示其为一个局部静态变量。另外,如果在函数名前加上static,则表示此函数只能在当前文件中被调用。(3)未初始化数据区。亦称BSS区(un
6、initia1izeddatasegment)存入的是全局未初始化变量。BSS这个叫法是根据一个早期的汇编运算符而来,这个汇编运算符标志着一个块的开始。BSS区的数据在程序开始执行之前被内核初始化为O或者空指针(NU11)。例如一个不在任何函数内的声明:1 1ongsum1000;将变量sum存储到未初始化数据区。下图所示为可执行代码存储时结构和运行时结构的对照图。一个正在运行着的C编译程序占用的内存分为代码区、初始化数据区、未初始化数据区、堆区和栈区5个部分。再来看一张图,多个一个命令行参数区:(1)代码区(textsegment)o代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执
7、行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。代码段:代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。代码区的指令中包括操作码和要操作的对象(或对象地址引用,如果是立即数(即具体的数值,如5),将直接包含在代码中;如果是局部数据,将在栈区分配空间,然后引用该数据地址;如果是BSS区和数据区,在代码中同样将引用该数据地址。另外,代码段还规
8、划了局部数据所申请的内存空间信息。(2)全局初始化数据区/静态数据区(DataSegment)。只初始化一次。数据段:数据段(datasegment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。data段中的静态数据区存放的是程序中已初始化的全局变量、静态变量和常量。(3)未初始化数据区(BSS)o在运行时改变其值。BSS段:BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Bk)CkStartedbySymbOI的简称。BSS段属于静态内存分配,即程序一开始就将其清零了。一般在初始化时BSS段部分将会清零。
9、(4)栈区(stack)。由编译器自动分配释放,存放函数的参数值、局部变量的值等。存放函数的参数值、局部变量的值,以及在进行任务切换时存放当前任务的上下文内容。其操作方式类似于数据结构中的栈。每当一个函数被调用,该函数返回地址和一些关于调用的信息,比如某些寄存器的内容,被存储到栈区。然后这个被调用的函数再为它的自动变量和临时变量在栈区上分配空间,这就是C实现函数递归调用的方法。每执行一次递归函数调用,一个新的栈框架就会被使用,这样这个新实例栈里的变量就不会和该函数的另一个实例栈里面的变量混淆。栈(StaCk):栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“”中定义的变量(
10、但不包括static声明的变量,static意味着在数据段中存放变量除此以外,在函数被调用时,其参数也会被压人发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。(5)堆区(heap)。用于动态内存分配。堆在内存中位于bss区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收。堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用ma11oc等函数分配内存时,新分配的内存就被
11、动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减在将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和BSS段的加载,并将在内存中为这些段分配空间。栈段亦由操作系统分配和管理,而不需要程序员显示地管理;堆段由程序员自己管理,即显式地申请和释放空间。另外,可执行程序在运行时具有相应的程序属性。在有操作系统支持时,这些属性页由操作系统管理和维护。C语言程序编译完成之后,已初始化的全局变量保存在DATA段中,未初始化的全局变量保存在BSS段中。TEXT和DATA段都在可执行文件中,由系统从可执行文件中加载;而BSS段不在可执行文件中,由系统初始化
12、。BSS段只保存没有值的变量,所以事实上它并不需要保存这些变量的映像。运行时所需要的BSS段大小记录在目标文件中,但是BSS段并不占据目标文件的任何空间。以上两图来自于C语言专家编程。在操作系统中,一个进程就是处于执行期的程序(当然包括系统资源),实际上正在执行的程序代码的活标本。那么进程的逻辑地址空间是如何划分的呢?左边的是UNIX/1INUX系统的执行文件,右边是对应进程逻辑地址空间的划分情况。首先是堆栈区(StaCk),堆栈是由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。栈的申请是由系统自动分配,如在函数内部申请一个局部变量inth,同时判别所申
13、请空间是否小于栈的剩余空间,如若小于的话,在堆栈中为其开辟空间,为程序提供内存,否则将报异常提示栈溢出。其次是堆(heap),堆一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收0注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。堆的申请是由程序员自己来操作的,在C中使用ma11oc函数,而C+中使用new运算符,但是堆的申请过程比较复杂:当系统收到程序的申请时,会遍历记录空闲内存地址的链表,以求寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,此处应该注意的是有些情况下,新申请的内存块的首地址记录本次分配的内存块大小,这样在
14、de1ete尤其是de1ete。时就能正确的释放内存空间。接着是全局数据区(静态区)(static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。另外文字常量区,常量字符串就是放在这里,程序结束后有系统释放。最后是程序代码区,放着函数体的二进制代码。为什么要这么分配内存?(1) 一个进程在运行过程中,代码是根据流程依次执行的,只需要访问一次,当然跳转和递归有可能使代码执行多次,而数据一般都需要访问多次,因此单独开辟空间以方便访问和节约空间。(2)临时数据及需要再次使用的代码在运行时放入栈区中,生命周期短。
15、(3)全局数据和静态数据有可能在整个程序执行过程中都需要访问,因此单独存储管理。(4)堆区由用户自由分配,以便管理。举例说明内存分布情况1*memory_a11。Catec用于演示内存分布情况*/23 inta=0;4 char*p1;*/56 intmain(void)7 intb;8 chars=abc,;9 *,abc”为字符串常量,10 char*1,2;11 char*p3=,12345;区,p3在栈区*/12 staticintc=0;己初始化数据区*/*a在全局已初始化数据区*/*P1在BSS区(未初始化全局变量)*b在栈区*/*S为数组变量,存储在栈区*/存储在己初始化数据区*/*p1、p2在栈区*/*”1234560”已初始化在数据*C为全局(静态)数据,存在于13/*另外,静态数据会自动初始化*/14p1=(char*)ma11oc(10);/*分配的10个字节的区域存在于堆区*/15p2=(char*)ma11oc(20);/*分配得来的20个字节的区域存在于堆区*/1617free(p1);18free(p2);19内存的分配方式在C语言中,对象可以