C 程序运行时的数据结构

一. a.out

    assembler output (汇编程序输出) 的缩写。

    在 SVr4 (UNIX System V Release4,UNIX 的内核标准) 中,可执行文件用文件的第 1 个字节来标注,文件以十六进制数 7F 开头,紧跟着的第 2 至第 4 个字节为 ELF。ELF 原意为 Extensible Linker Format (可扩展链接器格式),现在代表 Executable and Linking Format (可执行文件和链接格式)。


二. 段

    目标文件和可执行文件可以有不同的格式。SVr4 是 ELF 格式,其它系统中,可执行文件的格式是 COFF (Common Object-File Format,普通目标文件格式)。所有这些不同的格式,有一个共同的概念是段 (segments),它是二进制文件中简单的区域,保存的是和某种特定类型相关的所有信息。

    使用 size 命令,可以查看可执行文件中各个段的大小 (不包括堆栈段)。

1. 堆栈段

    堆栈是一块动态内存区域,实现了一种“后进先出”的结构。编译器设计者采用了一种稍微灵活的方法,可以从顶部入栈和出栈,也可以修改位于栈中部的变量的值。运行时系统维护一个指针 (常位于寄存器中),通常称为 sp (stack pointer),用于提示堆栈当前的顶部位置。堆栈段的三个主要用途包括:

    a. 为函数内部声明的局部变量提供存储空间,即 auto 变量。需要注意的是,auto 变量并不会进入目标文件,即,不会影响目标文件的大小,它们在运行时才创建

    b. 进行函数调用时,存储与此相关的一些维护性信息。这些信息被成为堆栈结构 (stack frame),又称为过程活动记录 (precedure activation recored)。它包括函数调用地址(即当所调用的函数结束后跳回的地方)、任何不适合装入寄存器的参数以及一些寄存器值的保存

    c. 可以用作临时存储区,例如计算一个很长的表达式,可以把部分计算结果压入堆栈,需要时再取出来

    除了递归调用外,堆栈并非必需。因为在编译时,可以知道局部变量、参数和返回地址所需空间的固定大小,然后将其分配于 BSS 段。

2. BSS 段

    Block Started by Symbol 的缩写,它是旧式 IBM 704 汇编程序的一个伪指令,Unix 借用了这个名字。

    BSS 段通常用于存放未初始化的全局变量静态变量,属于静态内存分配。在目标文件中,仅记录运行时所需的 BSS 段的大小,除此之外,BSS 段并不占据目标文件的任何空间。而程序开始运行时,BSS 段所需大小的内存被静态分配,并且被清零,故而未初始化的全局变量和静态变量在程序执行前已经初始化为 0 了。

    与数据段的区别是,BSS 段存放的是未初始化的全局变量和静态变量,而数据段存放的是初始化后的全局变量和静态变量。需要特别注意,这里所谓的初始化,不包括初始化为 0。即,如果一个全局变量在代码中被主动赋值为 0,它仍然保存在 BSS 段中,但如果被赋值为 1,才会从 BSS 段转换到数据段。原因是,BSS 段中的数据,会在程序启动后被初始化为 0,所以,即使被赋值为 0,目标文件也无需额外保存该信息。

    需要注意,不光全局变量会被自动初始化为 0,静态变量也是。所以:

    ......

    static int a;

    printf("%d\r\n", a);

    ......

    输出也是 0。

3. 数据段

    数据段属于静态内存分配,用于存储初始化后的全局变量和静态变量。

4. 文本段/代码段

    用于存放程序的执行代码,该区域的大小在程序运行前就已经确定,并且通常属于只读。在该段中,也可能包含一些只读的常数变量,如字符串常量等。




eg:

hello world


增加全局的 int array[1000]; 后,bss 段的大小增加了 4 * 1000 以上


将全局的数组初始化为0 int array[1000] = {0}; 后,bss 段的大小未发生改变


将全局的数组初始化为1 int array[1000] = {1}; 后,该数组从 bss 段转换到数据段


增加未初始化的局部数组,数据段和 bss 段大小都未变化


增加初始化为 0 的局部数组,数据段和 bss 段大小都未变化


增加初始化为 1 的局部数组,数据段和 bss 段大小都未变化


三. 执行

    段可以方便的映射到链接器在运行时可以直接载入的对象中。载入器只是取文件中每个段的映像,并直接将它们放入内存中。从本质上说,段在正在执行的程序中只是一块内存区域,每个区域都有特定的目的。

    文本段包含程序的指令。链接器把指令直接从文件拷贝到内存中 (一般使用 mmap() 系统调用),以后便不再管它,因为在典型情况下,程序的文本无论是内容还是大小都不会改变。有些操作系统和链接器甚至可以向段中不同的 section 赋予适当的属性,例如,文本段可以被设置为 read-and-execute-only,有些数据可以被设置为 read-write-no-execute,而另外一些数据则被设置为 read-only。




四. 过程活动记录

    C 语言自动提供的服务之一就是跟踪调用链——哪些函数调用了哪些函数,当下一个 return 语句执行后,控制将返回何处等。解决这个问题的机制是堆栈中的 过程活动记录。当每个函数被调用时,都会产生一个过程活动记录 (或类似的结构)。过程活动记录是一种数据结构,用于支持过程调用,并记录调用结束以后返回调用点所需要的全部信息。在不同的编译器中,该结构的具体细节不同。运行时系统维护一个指针 (常常位于寄存器中),通常成为 fp,用于提示活动堆栈结构,它的值是最靠近堆栈顶部的过程活动记录的地址。

    尽管谈到了“将过程活动记录压入到堆栈中”,但过程活动记录并不一定要存在于堆栈中。事实上,尽可能地把过程活动记录的内容放到寄存器中会使函数调用的速度更快,效果更好。SPARC 架构引入了一个概念,称为“寄存器窗口 (register window)”,CPU 拥有一组寄存器,它们只用于保存过程活动记录中的参数。每当函数调用时,空的活动记录依然压入到堆栈中。当函数调用链非常深而寄存器窗口不够用时,寄存器的内容就会被保存到堆栈中保留的活动记录空间中 (所以不建议非常深的调用链)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值