C语言程序内存布局

如有转载,请注明出处:http://blog.csdn.net/embedded_sky/article/details/44457453

作者:super_bert@csdn

一、几个概念

1.栈(Stack)

      C语言通过栈来维护函数调用上下文,也就是说C中的函数、函数参数列表、局部变量、函数返回值都保存在栈内存中,在完成函数调用之后栈帧随即销毁,至于具体的压栈顺序和上下文维护工作由谁来完成,则取决于函数的调用方式(cdecl/stdcall/fastcall/pascal).

      关于C函数调用约定,或者叫调用惯例(Calling Convention),以下表格中,个人认为出栈方应该叫做栈的维护方更确切,表格:C语言程序内存布局_第1张图片

     详细请看这里

      栈帧一般包括如下几方面内容:

      ①函数的参数列表和返回值(返回地址);

      ②临时变量:包括非静态局部变量和编译器自动生成的其他临时变量;

      ③保存的上下文:包括函数调用前后需保持不变的寄存器。

C语言程序内存布局_第2张图片

                                               栈增长示意图


2.堆(Heap)

      堆空间是需要程序自行申请的,简言之 在C语言程序中对空间由malloc函数族(malloc/alloc/calloc/realloc)申请,当然在C++中还有new关键字。堆空间由程序猿自行申请,也需由程序猿自行释放(此处,Javaer们笑看你们打脸)。malloc/free  new/delete分别对应申请和释放内存,new/delete是C++中的关键字,不是函数。

      通常情况下比较好的做法是,free掉之后,立即将指针置为NULL,因为通常if语句只会去判断指针是否为NULL(0x00000000),堆空间(内存)在free掉之后,该指针并不会指向NULL,也就是说free掉的是内存而不是指针,如果程序之后又用到该指针,后果将是不可预知的。如果被置为NULL,则可以被if语句检测出来,防止自己给自己留坑,C中的坑已经够多的了。

      glibc中的malloc函数是这样处理用户堆空间请求的:

      ①对于小于128KB的请求,在现有的堆空间里,按照分配算法为他分配一块空间并返回;

      ②对于大于128KB的请求,malloc内部会调用mmap()函数分配一块匿名空间,然后在这块匿名空间中为用户分配空间。

      mmap函数的具体用法请参考Linux  manpage,Windows环境下是没有mmap函数的,malloc函数族应该是依赖于 VirtualAlloc()函数申请堆空间。


3.动态链接库(Dynamic Libraries)

      Linux/Unix环境下应该是***.so之类的文件,Windows下则是DLL(Dynamic Linking Libraries)。


二、Linux进程地址空间

      先上图,箭头代表stack和heap的增长方向,dynamic libraries在2.4和2.6以后的内核中装载位置不一样,分别为0x40000000,0xBFXXXXXX.

C语言程序内存布局_第3张图片

      Linux采用了分页的内存管理机制。由于x86体系的分页机制是基于分段机制的,因此,为了使用分页机制,分段机制是无法避免的。为了降低复杂性,Linux内核将所有段的基址都设为0,段限长设为4G,只是在段类型和段访问权限上有所区分,并且Linux内核和所有进程共享1个GDT,不使用LDT(即系统中所有的段描述符都保存在同一个GDT中),这是为了应付CPU的分段机制所能做的最少工作。

      Linux内存管理机制可以分为3个层次,从下而上依次为物理内存的管理、页表的管理、虚拟内存的管理。



三、Windows进程地址空间

      相较Linux而言,Windows环境下的进程空间布局就显得"支离破碎",

C语言程序内存布局_第4张图片

      各位看官么看完这图估计会有点奇怪,一开始我也奇怪,为啥会有那么多stack呢?上面有说到,栈用于维护函数调用上下文,一个进程中调用函数所需存储的参数/变量/返回值,哪用得了那么多栈内存。

      造成需要这么多栈内存的原因如下,每个线程的栈空间都是独立的,而Windows程序一般都是多线程,很大一部分原因Windows的进程耗费资源太大,线程优化得比*nix要好得多。试想一下,若在一个进程中通过CreateThread创建了多个线程,然后每个线程的栈空间都是独立的,且可以通过CreateThread API的参数来指定(系统默认的每个线程栈大小为1M byte),所以一旦程序起起来,内存空间已经支离破碎了。

      然而我们的应用程序得在这个支离破碎的内存空间中去malloc堆(heap)空间,VirtualAlloc().

      malloc函数族在Windows环境下最终依赖于VirtualAlloc()函数实现,而VirtualAlloc()函数像操作系统申请的空间必须是页大小的整数倍,也就是说对于x86系统一次性申请的heap最少是4096个byte,OS就是这么定的。但是OS还提供了一个堆管理器,堆管理器每次向OS申请一大片heap空间,然后在申请到的空间里给应用程序进行malloc。

      HeapCreate创建一个堆;

      HeapAlloc从堆空间中申请内存;

      HeapFree释放内存;

      HeapDestroy销毁一个堆;

      相当于咱们从冷库里一次性批发了一箱冰激凌放在家里,咱们想吃的时候就一个一个去冰箱拿,想吃几个就拿几个。


此文只是抛砖引玉,其中很多地方都值得深入去研究、理解。

参考:http://www.cnblogs.com/zszmhd/archive/2012/08/29/2661461.html

x64系统程序内存布局请参考:http://blog.chinaunix.net/uid-27119491-id-3325943.html



你可能感兴趣的:(C)