编译与链接总结

编译与链接的过程可以分解为4 个步骤,分别是预处理(Prepressing)编译(Compilation)汇编(Assembly)链接(Linking)
编译与链接过程:
在这里插入图片描述

1.预处理

将源代码文件即后缀为.cpp和相关的头文件通过预处理器预处理为一个.i文件。
在Linux系统中,使用如下命令:
g++ -E helloworld.cpp -o helloworld.i
其中:-E的编译选项,意味着只执行到预编译,直接输出预编译结果。
预处理过程主要处理那些源代码文件只能够的以“#”开始的预编译指令:
(1)将所有的#define删除,并且展开所有的宏定义。
(2)处理所有条件预编译指令,比如#if、#ifdef、#elif、#else 、#endif。
(3)处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
(4)过滤所有的注释中的内容
(5)添加行号和文件名标识
(6)保留所有的#pragma编译器指令,因为编译器需要使用它们。
当无法判断宏定义是否正确或头文件包含是否正确时,可以查看预处理后的文件来确定问题。

2.编译

编译过程就是把预处理完的文件进行一系列的词法分析语法分析语义分析以及优化后产生相应的汇编代码文件
在Linux系统中,使用如下命令:
g++ -s helloworld .i -o helloworld.s
-S的编译选项,表示只执行到源代码到汇编代码的转换,输出汇编代码。
从最直观的角度来讲,编译器就是将高级语言翻译成机器语言的一个工具。
编译的过程一般分为6 步:扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成和目标代码优化。
编译过程:
在这里插入图片描述
以array[index]=(index+5)*(2+7) 为例:

a.词法分析

词法分析产生的记号一般可分为如下几类:关键字标识符字面量(包含数字、字符串等)特殊记号(如加号、等号)。在识别记号的同时,扫描器也完成了其他工作。比如将标识符存放到符号表中,将数字、字符串常量存放到文字表等,以备后面的步骤使用。
在这里插入图片描述

b.语法分析

整个分析过程采用了上下文无关文法的分析手段。由语法分析器生成的语法树就是以表达式为节点的树。
语法树:
在这里插入图片描述
符号和数字是最小的表达式,它们不是由其他的表达式来组成的,所以通常为整个语法树的叶节点。在语法分析的同时,很多运算符的优先级和含义也被确定下来。

c.语义分析

语义分析是由语义分析器完成。
编译器所能分析的语义是静态语义。所谓静态语义是指在编译期间可以确定的语义。与之对应的动态语义就是只有在运行期间才能确定的语义。静态语义通常包括声明和类型的匹配及类型的转换等。动态语义一般指在运行期间出现的语义相关的问题。
语义分析后的语法树:
在这里插入图片描述
经过语义分析阶段后,整个语法树的表达式都被标识了类型,如果有些类型需要做隐式转化,语义分析程序会在语法树中插入相应的转换节点。

d.中间语言的生成

现代的编译器有着很多层次的优化,往往在源代码级别会有一个优化过程。这里所描述的源码级优化器在不同编译器中可能会有不同的定义或一些其他差异。源代码优化器会在源代码级别进行优化。
直接在语法树上进行这类优化比较困难,所以源代码优化器往往将整个语法树转换成中间代码,它是语法树的顺序表示,已经非常接近目标代码了。但是中间代码一般跟目标机器和运行时环境是无关的,比如不包含数据的尺寸、变量的地址和寄存器的名字等。
优化后的语法树:
在这里插入图片描述
中间代码使得编译器可以被分成前端后端
编译器前端负责产生机器无关的中间代码,编译器后端则负责将中间代码转换成目标机器代码。这样对于一些可以跨平台的编译器而言,它们可以针对不同的平台使用同一个前端和针对不同的机器平台的数个后端

e.目标代码的优化和生成

源代码级优化器产生中间代码标志着下面的过程都属于编译器后端。编译器后端主要包括代码生成器目标代码优化器
代码生成器将中间代码转换成目标机器代码,这个代码十分依赖于目标机器。
生成目标代码后,目标代码优化器对上面目标代码进行优化,比如选择合适的寻址方式、使用位移来代替乘法等。

3.链接

定义其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接的时候才能确定。所以现代的编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件
把每个源代码模块独立地编译,然后按照要将它们“组装”起来,这个组装模块的过程就是链接
链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。就是把一些指令对其他符号地址的引用加以修正。
链接过程主要包括了地址和空间分配符号决议重定位等这些步骤。
每个模块的源代码文件(如.c 文件)经过编译器编译成目标文件(如.o 文件),目标文件和库一起链接形成最终可执行文件。
链接过程:
在这里插入图片描述
库其实就是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。
在链接过程中,对其他定义在目标文件中的函数调用的指令需要被重新调整,对使用其他定义在其他目标文件的变量来说,也存在同样问题。
对于上述情况会存在一个地址修正的过程,这个地址修正的过程也叫作重定位,每个要被修正的地方叫一个重定位入口
目标文件包括自身数据二进制代码3个表,其中3个表分别为:
1.未解决符号表:未解决符号表提供了所有在该编译单元里引用但是定义并不是在本编译单元的符号以及其出现的地址;
2.导出符号表:导出符号表提供了本编译单元具有定义,并且愿意提供给其他单元使用的符号及其地址。
3.地址重定向表:地址重定向表提供了本编译单元所有对自身地址的引用的记录。
编译器将extern声明的变量置入未解决符号表,而不置入导出符号表。这属于外部链接
编译器将static声明的全局变量不置入未解决符号表,也不置入导出符号表,因此其他单元无法使用。这属于内部链接
普通变化及其函数被置入导出符号表。
链接分为静态链接和动态链接。

a.静态链接

对函数库的链接是放在编译时期完成的是静态链接
程序在运行时,与函数库再无瓜葛,因为所有需要的函数已复制到相关位置。这些函数库被称为静态库,通常文件名为“ libxxx.a ”的形式。
编译和使用静态链接库的过程:
假设有这样的5个文件:add.h,add.cpp,sub.h,sub.cpp和main.cpp。
1.先将add.cpp和sub.cpp编译成.o文件,代码如下:
g++ -c add.cpp
g++ -c sub.cpp
生成的文件add.o sub.o, -c 的编译选项,表示只执行到编译,输出目标文件(无论是静态库文件还是动态库文件,都是由.0 文件创建的)。
2.由.o文件创建静态库( .a文件),执行命令:
ar cr libmymath.a sub.o add.o
会生成libmymath.a 文件,ar 命令显示库文件中的.o 文件。
库文件的命名规范是以lib开头(前缀),紧接着是静态库名,以.a 为后缀名。
ar命令的r选项:在库中插入模块(替换)。默认的情况下,新的成员增加在库的结尾处,可以使用其他任意选项来改变增加的位置。
ar命令的c选项:创建一个库。不管库是否存在,都将创建。
ar的tv参数, ar tv libxxx.a,可以显示库文件中有哪些目标文件,显示文件名、时间、大小等详细信息。
3.在程序中使用静态库,执行命令:
g++ -o main main.cpp -L. -lmymath
会生成main文件。
静态库制作完了, 要使用它内部的函数,只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后再用g++命令生成目标文件时指明静态库名。
g++会在静态库名前加上前缀lib ,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。
4.生成目标程序main ,执行main 文件

b.动态链接

除了静态链接,也可以把对一些库函数的链接载入推迟到程序运行时期(runtime),这就是动态链接库(dynamic link library)技术
动态库名增加前缀lib,但其文件扩展名为“.so” 。例如:将创建的动态库名为mymath后,则动态库文件名就是“libmymath.so”。
依然假设有这样的5个文件:add.h,add.cpp,sub.h,sub.cpp和main.cpp。
键入以下命令得到动态库文件“libmymath.so”:
g++ -fPIC -o add.o -c add.cpp
g++ -fPIC o sub.o -c sub.cpp
g++ -shared -o libmymath.so add.o sub.o
也可以直接一条命令
g++ -fPIC -shared -o libmymath.so add.cpp sub.cpp
常用的编译参数对应的功能如下所述:
-fPIC:表示编译为位置独立的代码。
-Lpath:表示在path目录中搜索库文件,如-L.则表示在当前目录。
-Ipath:表示在path目录中搜索头文件。
-ltest:编译器查找动态连接库时有隐含的命名规则。
在程序中隐式使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明。
程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。
动态库的搜索路径搜索的先后顺序:
1.编译目标代码时指定的动态库搜索路径;
2.环境变量LD_LIBRARY_PATH 指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf 中指定的动态库搜索路径;
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。

c.动态库与静态库重名问题

动态库文件和静态库文件同名的时候,编译器会先到path目录下搜索libxxx.so文件,如果没有找到,则继续搜索libxxx.a(静
态库)。

d.静态链接库、动态链接库各自的特点

1.动态链接库有利于进程间资源共享。
当某个程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有时才链接载入。
C语言的标准库就是动态链接库,也就是说系统中所有运行的程序共享着同一个C 语言标准库的代码段。而静态链接库则不同,如果系统中多个程序都要调用某个静态链接库函数时,则每个程序都要将这个库函数拷贝到自己的代码段中,这显然将占有更大的内存资源。
2.将一些程序升级变得简单。
用静态库,如果库发生变化,使用库的程序要重新编译;使用动态库,只要动态库提供给该程序的接口没变,只要重新用新生成的动态库替换原来就可以了。
3.可以真正做到链接载入完全由程序员在程序代码中控制。
程序员在编写程序的时候,可以明确的指明什么时候或者什么情况下,链接载入哪个动态链接库函数。
4.由于静态库在编译的时候,就将库函数装载到程序中去了,而动态库函数必须在运行的时候才被装载,所以程序在执行的时候,用静态库速度更快些。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值