阿龙的学习笔记---《程序员自我修养-链接、装载与库》读书笔记(一)
记录笔记,因为看了好久还没看完…真的是,但是虽然后面的没看完,但我前面的页快忘完了呀,记录一下吧还是。
一、 温故而知新
- 从计算机组成原理之类的开始讲,说明这部分还是蛮重要的,虽然也快忘完了。
- 讲到操作系统,这个的确也与程序运行息息相关。
- 讲到了一个线程安全与编译器的问题,虽然放在这怪怪的,但是很有趣:
-
虽然如我们一般所知,加锁是可以在简单情况下保证线程安全的,但是假如编译器为了提高运行速度而优化,假如将一个变量先放在了寄存器中,然后暂时不写回,过了一段时间才写回内存,那么这个就会有问题了,即使正确加锁也线程不安全了。
-
上面这个问题,C/C++的volatile可以解决,他做两件事:
- 阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回。
- 阻止编译器调整操作 volatile变量的指令顺序。
-
再有一种情况是:编译器会优化语句执行的顺序,那么就没有太优雅的方式,通常情况下是调用CPU提供的一条指令,这条指令常常被称为 barrier,barrier指今会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。
-
二、 编译和链接
-
从代码到执行
- 从hello.c到a.out,有这么几个步骤:分别是预处理( Prepressing),编译(Compilation)汇编(Assembly)和链接( Linking)。
- 预编译:主要处理代码中以“#”开头的预编译指令。大概就是展开#include,展开#define宏定义,处理#if,#ifdef等条件预编译,删除所有注释,添加行号和文件名标识。.c文件会被预编译成.i文件。
- 编译:是把预处理的文件进行一系列的 词法分析、语法分析、语义分析、优化操作,生成汇编代码。是程序构建的核心部分。.i 文件会被便以为 .s 文件。
- 汇编: 汇编器将汇编代码转变为机器可以认识的代码,机器代码,只用按照汇编指令和机器指令的对照表一一翻译即可。输出目标文件 ,即.o 文件。
- 链接:输出的目标文件运行时,仍然需要其他代码的支持。比如你添加一个头文件,为啥就能调用头文件里的函数了呢?因为有链接器,让你的程序能够找到那些函数、变量等。同样对于调用printf函数也需要链接。
-
编译部分做了什么?
- 词法分析:源代码输入进来之后,要分词,比如要把a=2; 分解为a, =, 2三部分,类型是标识符、值符号、数字。
-
语法分析:对上面的记号进行分析,C语言的一个语句是一个表达式,复杂的语句则是多个表达式的结合,则需要把表达式分开,分解成语法树的方式是利于计算机分析的。
- 比如:
- 根据符号的优先级等规则,会被分解为:
- 比如:
- 语义分析:上面只是分析出了语句是什么样的,并不知道是否合理,有意义。语义分析通常包括声明和类型的匹配,类型转换。
- 目标代码生成:编译器把优化得来的中间代码转换为虎目标机器代码,根据不同的机器,会有不同的类型程度等。
-
链接部分做什么?
- 此处先看静态链接。程序模块化之后,不同模块之间独立编译,链接这个步骤就是将各模块之间组合起来。
- 链接主要包括了:地址和空间分配、符号决议、重定位这些步骤。
- 比如我们使用到foo.c中的一个函数foo(),我们main.c中调用时,必须要知道这个foo()函数的地址,编译期间是不知道的,那么需要先搁置(置为0),等最后链接的时候再去修正这些地址。
- 修正地址的过程叫做重定位,每个地方叫重定位入口。
三、目标文件
-
目标文件是编译完成后的文件,从结构上说已经是可执行文件的格式了,只是还没调整一些符号的地址等。
-
Linux下可执行文件的格式是ELF文件。
-
目标文件里面有编译过后的代码指令、数据,还有一些链接时用的信息比如符号表、调试信息等。
-
目标文件一般以段(Segment)的形式存储,比如编译后的机器指令被放在代码段.text,全局变量局部静态变量数据经常被放在数据段.data。
-
linux下的目标文件是ELF文件,大致看一下ELF的结构:
- 文件头:ELF的文件头中定义了ELF魔数、文件机器字节长度数据存储方式、版本、运行平台、AB版本、ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的位置和长度及段的数量等。这些数值中有关描述ELF目标平台的部分,与我们常见的32位 Intel的硬件平台基本上一样。
- 段表:我们知道ELF文件中有很多各种各样的段,这个段表(Section Header Table)就是保存这些段的基本属性的结构。段表是ELF文件中除了文件头以外最重要的结构,它描述了ELF的各个段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其他属性。
- 重定位表:链接器在处理目标文件时,须要对且标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个须要重定位的代码段或数据段,都会有一个相应的重定位表。、
-
链接的接口——符号
- 链接需要处理的就是符号,也就是函数和变量,统称为符号。每一个目标文件会有相应的符号表,值就是符号的地址。
-定义在本文件的全局符号,可以被其他文件引用;而本文件中引用的全局符号,但没在本文件定义,叫做外部符号。 - 符号表.symtab会有符号的具体信息。
- C++由于引进了复杂特性,符号增加了符号修饰,来解决函数重载、继承、虚拟机制等问题导致的符号重名。
- 链接需要处理的就是符号,也就是函数和变量,统称为符号。每一个目标文件会有相应的符号表,值就是符号的地址。
-
目标文件中还有可能保存一些调试信息,调试时必须提前知道源代码和目标代码之间的关系,来设置断点、监控变量。(例如Qt中可以选择Debug版本)但是比较占空间。