2.程序员的自我修养---编译和链接

1.编译过程
	1.预处理
		gcc -E hello.c -o hello.i
		源码文件和相关头文件等,被预编译器 cpp 预编译成一个 .i 文件。

		预编译过程主要处理那些源代码文件中的以 "#" 开始的预编译命令。如 "#include", "define"等,
		规则如下:
			1.将所有的 "#define" 删除,并且展开所有的宏定义
			2.处理所有条件预编译指令,比如 "#if", "#ifdef", "#elif", "#else", "#endif"
			3.处理 "#include" 预编译指令,将被包含的文件插入到该预编译指令的位置
			4.删除所有的注释 "//" 和 "/* */"
			5.添加行号和文件名标识,比如 #2 "hello.c" 2, 以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时能够产生行号
			6.保留所有的 #pragma 编译器指令,因为编译器必须要使用它们

	2.编译
		gcc -S hello.i -o hello.s
		编译的过程就是把预编译处理完的文件进行一系列的词法分析,语法分析,语义分析以及优化后生成响应的汇编代码文件。

	3.汇编
		gcc -c hello.s -o hello.o
		汇编器是将汇编代码转变为机器可执行的指令,每个汇编语句几乎都对应一条机器指令。

	4.链接
		ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o

array[index] = (index + 4) * (2 + 6)

2.编译器做了啥
	编译过程:
		1.词法分析(生成 token)
			源代码被输入到 扫描器(scaner),扫描器的任务很简单,它只是简单的进行词法分析,运用一种类似于有限状态机的算法可以很轻松的将源代码的字符串序列
		  分割成一系列的 记号(token) 。
		    词法分析产生的记号一般可以分为如下几类:关键字,标识符,字面量(包含数字,字符串等)和特殊符号(如加好,等号)。
		    有个叫 lex 的程序可以实现词法扫描.
		    array
		    [
		    index
		    ]
		    =
		    (
		    index
		    +
		    4
		    )
		    *
		    (
		    2
		    +
		    6
		    )

		2.语法分析(生成语法树)
			接下来语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树。整个分析过程采用了上下文无关语法的分析手段。语法分析器生成
		  的语法树就是以表达式为节点的树。
		    yacc 词法分析工具

		3.语义分析(还是语法树)
			语义分析器(Semantic Analyzer)仅仅是完成了对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义。编译器所能分析的语义是静态语义,
		  所谓的静态语义是指在编译期可以确定的语义,与之对应的是动态语义就是只有在运行期才能确定的语义。
		    静态语义通常包括声明和类型的匹配,类型的转换。
		    动态语义一般是指在运行期出现的语义相关的问题,比如将0作为除数是一个运行期语义错误。

		4.中间语言生成(对语法树进行优化,生成与机器无关的代码)
			现代的编译器有着很多层次的优化,往往在源代码级别会有一个优化的过程。我们这里描述的源码级优化器( Source Code Optimizer) 在不同的编译器中可能
		  会有不同的定义或有一些其他的差异。比如表达式 (2 + 6)就可以优化,编译期就可以确定。
		    我们看到 (2 + 6)这个表达式被优化成8。其实直接在语法树上面优化比较困难,所以源代码优化器往往是将整个语法树转换成成中间代码(Intermediaet Code),
		  它是语法树的顺序表示,其实它已经非常接近目标代码了。但是它一般跟目标机器和运行时环境是无关的,比如它不包含数据的尺寸,变量地址和寄存器的名字等。中间
		  代码有很多种类型,在不同的编码器中有不同的形式,比较常见的有:三地址码(Three-address Code)和 P-代码(P-code)。
		    三地址码: x = y op z
		    上面的语法树可以被翻译成三地址码 :
		    t1 = 2 + 6   
		    t2 = index + 4
		    t3 = t2 * t1
		    array[index] = t3
		    
		    t1 这个时候会被优化成 8, 优化后为:
		    t2 = index + 4
		    t2 = t2 * 8
		    array[index] = t2

		    中间代码使得编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对于一些可以跨平台的编译器而言,
		  它们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端。

		5.目标代码的生成和优化
			源代码级别优化器产生中间代码,标志着下面的过程都属于编译器后端。编译器后端主要包括代码生成器(Code Generator) 和 目标代码优化器( Target Code Optimizer)。
		  代码生成器将中间代码转换成目标机器代码,这个过程十分依赖于目标机器,因为不同的机器有着不同的字长,寄存器,整数数据类型和浮点数据类型。

		有可能的代码:
		movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8

        	最后目标代码优化器对上述的目标代码进行优化,比如选择合适的寻址方式,使用位移来代替乘法运行,删除多余的指令等。

        	忙活了这么多步骤,index 和 array 的地址还没确定。目标代码中有变量定义在其他模块,怎么办?
        	事实上,定义其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接时才能确定。所以现代的编译器可以将一个源代码文件编译成一个未链接的目标文件,
          然后由链接器最终将这些目标文件链接成可执行文件。
1.一个程序被分割成多个模块后,这些模块之间如何形成单一程序是必须解决的问题。
  模块之间如何组合的问题,可以归结为模块之间如何通信的问题。常见的方式:
  1.模块间函数的调用
  2.模块间变量的访问
  函数访问需要知道函数的地址,变量访问也需要变量的地址。所以2种方式归结为一种,那就是对模块间符号的引用。


2.模块拼装---静态链接
	链接的主要内容就是把各个模块之间互相引用的部分处理好,使得各个模块之间能够正确的衔接。
	链接过程包括了:
		1.地址和空间的分配
		2.符号决议
			符号决议有时候也叫符号绑定,名称绑定,名称决议,甚至叫地址绑定,指令绑定。大体上是一样的。但从细节上区分,
		   它们还是存在一定差异,比如 "决议"更倾向静态链接,而 "绑定"更倾向于动态链接,即它们所指的范围不一样。在静态链接
		   我们统一称为符号决议。
		3.重定位

  静态链接的基本过程和作用 :
  	目标文件和库一起链接,最终形成可执行文件。而最常见的库就是运行时库,它是支持程序运行的基本函数的集合。库其实是一组目标文件的包,
  就是一些最常用的代码编辑成目标文件后打包存放。
  	比如我们在程序模块 main.c 中使用了另外一个模块 func.c 中的函数 foo()。我们在 main.c 模块中每一处调用 foo 的时候就必须知道
  foo 这个函数的地址。但是由于每个模块都是单独编译的,编译器编译 main.c 的时候并不知道 foo 函数的地址,所以它暂时把这些调用 foo
  的指令的目标地址搁置,等待最后链接的时候,由链接器去将这些指令的目标地址修正。
    如果没有链接器,须要我们手工把每个调用的 foo 的指令修改,填入正确的 foo 函数地址。当 func.c 模块被重新编译的时候,foo 函数地址
  可能被改变,那么我们在 main.c 中所有使用到的 foo 的地址指令将全部重新调整。这些繁琐的工作将成为程序员的噩梦。
    使用链接器,你可以直接引用其他模块的函数和全局变量而无需需要它们的地址,因为链接器在链接的时候,会根据你引用到的 foo 的指令重新修正。
  让它们的目标地址为真正的 foo 函数地址。

 

1.预编译

 

2.编译

3.汇编

 

4.链接

 

5.词法分析

 

 

6.语法分析

 

7.语义分析

 

8.中间语义生成

 

9.目标代码生成和优化

 

 

2.编译器

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值