C专家编程(学习笔记)_第6章 运动的诗章:运行时数据结构

       编程语言理论的经典对立之一就是代码和数据的区别,有的语言把两者视为一体,其他语言(例如C语言)通常维持两者的区别。代码和数据的区别也可以认为是编译时和运行时的分界线。编译器的绝大部分工作都是跟翻译代码有关;必要的数据存储管理的绝大部分都在运行时进行。

      学习运行时系统,主要有3个理由:(1)它有助于优化代码,获得最佳的效率;(2)它有助于理解更高级的材料;(3)当陷入麻烦时,它可以使分析问题更加容易。

1.a.out及其传说

       a.out  -汇编程序和链接编辑输出格式        它不是汇编程序输出,而是链接器输出。

       UNIX中的可执行文件也是以一种特殊的方式加上标签,这样系统就能确认它们的特殊属性。

2.段

       目标文件和可执行文件可以有几种不同的格式,在绝大多数SVr4实现中都采用了一种称作ELF的格式。在其他系统中,可执行文件的格式COFF。在BSD UNIX中a.out文件具有a.out格式,可以通过键入man a.out在主文档中查看更多有关UNIX系统所使用的格式信息。所有这些不同格式具有一个共同的概念,那就是段。

       在UNIX中,段表示一个二进制文件相关的内容块。在Intel x86的内存模型中,段表示一种设计的结果。

       BSS段(Block Start by Symbol)的缩写,由于BSS段只保存没有值的变量,所以事实上它并不需要保存这些变量的映像,运行时所需要的BSS段大小记录在目标文件中,但BSS段(不像其他段)并不占据目标文件的任何空间。

       数据段保存在目标文件中;BSS段不保存在目标文件中(除了记录BSS段在运行时所需要的大小);文本段是最容易受优化措施影响的段;a.out文件的大小受调试状态下编译的影响,但段不受影响。

3.操作系统在a.out文件里干了些什么

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

       数据段包含经过初始化的全局和静态变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段之后。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常统称为数据区。这是因为在操作系统的内存管理术语中,段就是一片连续的虚拟地址,所以相似的段被结合,一般情况下,在任何进程中数据段是最大的段。

       注意虚拟地址空间的最低部分未被映射,也就是说,它位于进程的地址空间内,但并未赋予物理地址,所以任何对它的引用都是非法大的。在典型情况下,它是从地址零开始的几K字节,它用于补捉使用空指针和小整数值的指针引用内存的情况。

4. C语言运行时系统在a.out里干了些什么

       运行时数据结构有好几种:堆栈、活动记录、数据、堆等。

       堆栈段包含一种单一的数据结构——堆栈(后进先出的一种结构)。堆栈段有三个主要的用途,其中两个跟函数有关,另一个跟表达式计算有关:(1)堆栈为函数内部声明的局部变量提供存储空间,按照C语言术语,这些变量被称为“自动变量”;(2)进行函数调用时,堆栈存储与此有关的一些维护信息;(3)堆栈也可以被用作暂时存储区,有时候程序需要一些临时存储,比如计算一个很长的算术表达式时,它可以把部分计算结果压到堆栈中,当需要时再把它从堆栈中取出。

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

      在不同的计算机架构和不同的操作系统中,堆栈的位置可能各不相同。事实上在绝大多数处理器中,堆栈是向下增长的,也就是朝着低地址方向生长。

5.当函数被调用时发生了什么:过程活动记录

      堆栈中的过程活动记录,当每个函数被调用时,都会产生一个过程活动记录(或类似的结构)。过程活动记录是一种数据结构,用于支持过程调用,并记录调用结束以后返回调用点所需要的全部信息。

过程活动记录的规范描述:局部变量——>参数——>静态链接——>指向先前结构的指针——>返回地址.

6.auto和static关键字

       如果想返回一个指向在函数内部定义的变量的指针时,要把那个变量声明为static,这样就能保证该变量被保存在数据段中而不是堆栈中,该变量的生命周期就和程序一样长,当定义该变量的函数退出时,该变量的值依然定义能保持。当该函数下一次进入时,该值依然有效。存储类型说明符auto关键字在实际中从来用不着。

7.控制线程

       对于如何在进程中支持不同的控制线程(轻量级进程)是比较清楚的,只要简单地为每个控制线程分配不同的堆栈即可。线程是一种非常强大的编程模式,即使在单个处理器上也可以提高性能。

8.setjmp和longjmp

       setjmp(jmp_buf j)必须首先被调用,它表示“使用变量j记录现在的位置,函数返回零”。

       longjmp(jmp_buf j,int i)可以接着被调用,它表示“回到j所记录的位置,让它看上去像是从原先的setjmp()函数返回一样。但是函数返回i,使代码能知道它是实际上是通过longjmp()返回的”。

       当使用于longjmp()时,j的内容被销毁。setjmp保存了一份程序的计数和当前的栈顶指针。goto语句不能跳出C语言当前的函数。用longjmp()只能调回到曾经到过的地方。

      需要注意的是,保证局部变量在longtime过程中一直保持它的值的唯一可靠方法是把它声明为volatile。

      setjmp/longjmp最大的用途是错误恢复。

9.UNIX中的堆栈段

       在UNIX 中,当进程需要更多空间时,堆栈会自动生长。当试图访问当前系统分配给堆栈的空间之外时,它将产生一个硬件中断,称为页错误(page fault)。处理页错误的方法有好几种,取决于对页面引起是否有效。

10.MS-DOS中的堆栈段

       在DO中,在建立可执行文件时,堆栈的大小必须同时确定,而且它不能再运行时增加。确定堆栈大小的方法根据所使用的不同编译器而不同。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值