陈意涵全身图片:关于C程序运行时的内存 的一些理解【转】

来源:百度文库 编辑:九乡新闻网 时间:2024/04/26 23:20:42

学习过C语言的同学应该都知道内存是什么,以及怎么用。但是程序代码编译目标文件和可执行文件是什么格式?程序在内存里是怎么组织的呢?分为哪几个部分?堆栈有哪些具体功能?当函数调用时发生了什么?等等问题。并不一定每个人都很清楚。下面就写点我以前看书时的日记和理解。

一、             首先来讲一下目标文件和可执行文件的格式

不同的系统的可执行文件有不同的格式。在SVr4实现中都采用了ELF(Extensible and Linker Format,可执行文件夹和链接格式)的格式,在其他系统中,可执行文件的格式是COFF(Common Object-File Format,普通目标文件格式,在BSD UNIX中也有自己自己的格式。可以通过命令man a.out 查看。这里就不详细介绍了。

但是所有这些不同格式都有一个共同的概念,那就是段(segments)。它是二进制文件中简单的区域,里面保存了和某些特定类型相关的所有信息,可以通过size a.out来查看:

[root@localhost ~]# size a.out

   text    data     bss     dec     hex filename

   1197     512       8    1717     6b5 a.out

可以看出来有(text文本段  data 数据段  bss bss段)

其中文本段保存的是可执行文件的指令,数据段保存经过初始化的全局和表态变量,BSS段保存没有值的变量。但BSS段和前两个不同,它并不占据目标文件的任何空间。

下面我们通过一些实例来验证一下:

1)  写一个最简单的”hello world”程序并编译成a.out

[root@localhost ~]# ls -l a.out

-rwxr-xr-x 1 root root 6664 05-19 17:08 a.out

[root@localhost ~]# size a.out

   text    data     bss     dec     hex filename

   1079     504       8    1591     637 a.out

2)增加一个全局的int[1000]数组声明,重新编译:

[root@localhost ~]# ls -l a.out 

-rwxr-xr-x 1 root root 6690 05-19 17:10 a.out

[root@localhost ~]# size a.out  

   text    data     bss     dec     hex filename

   1079     504    4032    5615    15ef a.out

3)现在给数组赋初值。这将使数组从BSS段转换到数据段:

[root@localhost ~]# ls -l a.out

-rwxr-xr-x 1 root root 10714 05-19 17:13 a.out

[root@localhost ~]# size a.out 

   text    data     bss     dec     hex filename

   1079    4520       8    5607    15e7 a.out

分析:比较第二步和第一步,发现BSS段增加了很多,基本上的数组空间(4*1000),但对比较a.out的大小,没有很明显的变化。所以可以看出BSS段并不占目标文件的空间;再来看第三步的结果,BSS段的大小又回到第一步的大小(因为全局数组初始化了,从而转到了数据段),数据段增加了4000多。再来看a.out在大小,是不是也增加了4000多。所以可以得出结论:数据段保存在目标文件中,BSS段不保存在目标文件中。

二、             目标文件和可执行文件是怎么映射到内存的

前面我们知道了目标文件是以段的形式组织的,那么为什么要这样做呢?那是因为段可以方便的地映射到链接器在运行时可以直接载入的对象中。载入器只是取文件中的每个段的映像,并直接将它们放入到内存,从本质上说,段在正在执行的程序中是一块内存区域,每个段都有特定的目的,下图是一个各个段和内存的简单的映射关系,接下联系这个图逐个的介绍它们的作用

 

1)      文本段,文件段包含程序的指令。链接器把指令直接从文件拷贝到内存中(一般使用mmap()系统调用),然后便再也不用管它,每个段的内容还可以赋予不同的属性,比如文件段一般是只允许读和执行,数据段有些是允许读和写,但不允许执行;还有一些可能是只读。

2)      数据段,数据段包括经过初始化的全局和静态变量以及他们的值,

3)      BSS段,BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块。紧跟在数据段之后,包括数据段和BSS段整个区域统称为数据区,这是因为在操作系统的内存管理术语中,段就是一片连续的虚拟地址地址。

4)      从上图中可以看到,一个即将执行的程序可能还需要一些内存空间,用于保存局部变量、临时变量、传递到函数中的参数。堆栈段就是用于这个目的,另外还需要堆(heap)空间,用于动态分配内存

三、             堆栈段有哪些具体作用

前面已经说了堆栈段是用于保存局部变量、临时变量、传递到函数中的参数等的。它其实包含了一种单一的数据结构——堆栈,那么堆栈又怎么理解呢?堆栈是实现“先进后出”的结构。打个生活中的比方:好比自助餐厅里叠在一起的盘子,盘子可以放置任意数量(不用考虑它会倒)。但你只能从顶部放或取一个盘子。也就是说你可以在顶部加盘子,也可以取出。堆栈有点不同的是。你不止可以取顶部“盘子”的值。还可修改顶部的“盘子”的值,还可以修改任意位置盘子的值。那么它到底有什么具体的用处呢?

1)  堆栈为函数内部声明的局部变量提供存储空间,

2)  进行函数调用时,堆栈存储与此有关的一些维护性信息,了称之为“过程活动记录”,详情在后一节讨论

3)  堆栈也可以补用作暂时存储区。如一个比较长的算术表达式,可能会把部分结果压到堆栈中。当需要的时候再把它从中取出。另,递归调用是用肯定全用到堆栈的。具体怎么回事也在后面讨论。

四、             当函数调用时的“过程活动记录”

那么什么是过程活动记录呢?过程活动记录是一种数据结构,用于支持过程调用,并记录调用结束以后返回调用点所需要的全部信息,也就是在你调用了函数后。知道此函数什么返回到什么地方。举个例子来说明:

 A(int i){

       If(I <>0)

              A(--i)

       Else

              Printf(“I has reached zero”);

       Return;

}

Int main(){

       A(1);

}