c++从源文件到可执行文件的步骤详解(转)
编译与链接有四个过程:
(1)预处理
(2)编译
(3)汇编
(4)链接
(1)预处理
源文件和头文件被预处理成一个.i文件、(-E表示只进行预处理)
g++ -E hello.cpp -o hello.i
-E:意味着只执行到预编译,直接输出预编译结果。
预处理过程主要处理那些源文件中的以“#”开始的预编译指令。包括#include,#define, #if,等等。
主要的处理规则如下:
(1)将所有的#define删除,并且展开所有的宏。
如#define a b 就是将所有的a替换成b。但作为字符串常量a则不替换。
(2)处理所有的条件预编译指令,,如#if,#ifdef,#else,#endif(以此来决定对哪些代码进行处理,将那些不必要的
代码过滤掉)
(3)处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。()这个过程是递归进行的。其中系统提供的头文件一般放在/usr/include下面,用<>表示。开发人员自定义的头文件放在与源程序同一个目录下,用“”表示。
(4)过滤所有的注释“//“和”/* */“之间的内容。
(5)添加行号和文件名标识。比如 #2 "test.c" 2
(6)保留所有的#pragma编译器指令,因为编译器需要使用他们。(下面是pragma的一些参数,详情看 https://baike.baidu.com/item/%23pragma)
(2) 编译,汇编
编译过程就是把预处理的文件进行一系列的词法分析,语法分析,语义分析以及优化后产生相应的汇编代
码文件。相当于:
g++ -S hello.i -o hello.s
-S(大写):表示只执行到源代码到汇编代码的转换,输出汇编代码。
编译器就是将高级语言翻译成机器语言的一个工具。
编译过程分为6步:
词法分析(扫描)
语法分析
语义分析
源代码优化
(其实应该上面四个才叫编译)
(下面两个叫汇编了)
代码生成
目标代码优化
由一个例子来分析:
array[index]=(index+5)*(2+7)
(1)词法分析(扫描)
运用类似于有限状态机的算法将源代码的字符分割成一系列的记号。如上面的,一共包含28个非空字符
串,产生了16个记号。
词法分析产生的记号一般分为几种:
关键字
标识符
字面量(数字,字符串等)
特殊记号(加号,等号等)
另外,扫描器也完成其他一些工作,比如将标识符存放到符号表中,将数字,字符串常量存放到文字
表中
词法分析工具(lex)
(2)语法分析
将由扫描器产生的记号进行语法分析,从而产生语法树。
语法树:以表达式为结点的树。(c语言中,一个语句就是一个表达式)
另外,在语法分析时,很多运算符的优先级和含义也被确定下来。
语法分析工具(yacc)
(3)语义分析
就是看看这个语句是否有意义。比如两个指针相乘,这是没有意义的,不过在语法分析的时候是合法的。
编译器能分析的语义是静态语义
静态语义:在编译期间可以确实能的语义
动态语义:在运行期间才能确定的语义,比如将0作为除数是一个运行期语义错误。
静态语义通常包括声明和类型的匹配以及类型的转换。
如果有写了类型转换需要做隐式转化,语义分析程序会子啊语法中插入相应的转换节点。
语义分析也对符号表里面的符号做了更新
(4)中间语言的生成
源代码优化器会在源代码级别进行优化。上面那个例子中,(2+7)被优化成9。
中间代码一般跟目标机器和运行时环境是无关的,比如不包含数据的尺寸,变量的地址和寄存器的名字等
等。
中间代码使得编译器可以被分为前端和后端。
前端:负责产生机器无关的中间代码
后端:负责将中间代码转换为目标机器代码。
这样,对于一个跨平台的编译器而言,可以针对不同的平台使用同一个前端和针对不同的机器平台的后端
个数
(5)目标代码的生成与优化。(这两个其实就是汇编)
编译器后端包含:
代码生成器(汇编):将中间代码转换成目标机器代码,这个代码十分依赖于目标机器,因为不同的机器有着
不同的字长,寄存器,整数数据类型和浮点数数据类型等。
目标代码优化器:对上面的代码进行优化,选择合适的寻址方式,使用位移来代替乘法等。
对于index和array,如果他们定义在和源代码同一个编译单元里面,那么编译器可以为他们分配空
间,确定他们的地址。如果他们定义只其他模块里面,(定义在其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接的时候才能确定)
(3)链接
从原理上来说,是把一些指令对其他符号地址的引用加以修正。
链接过程主要包括:
地址和空间分配
符号决议
重定位
库就是一组目标文件的包,就是一些常用的代码编译成目标文件以后打包存放。
重定位:就是一开始编译器在不知道变量的目标地址的情况下,先将目标地址设为0,链接以后再将这个地址修改为它真正的目标地址。这个地址修正的过程就叫做重定位。
每个目标文件除了拥有自己的数据和二进制代码以外,还提供三个表:
(1)未解决符号表:提供了所有在该编译单元引用但是定义不是在本编译单元的符号以及其出现
地址。
(2)导出符号表:提供了本编译单元具有定义,并且愿意提供给其他单元使用的符号及其地址。
(3)地址重定向表:提供了本编译单元对所有对自身地址的引用的记录。
编译器将extern声明的变量置入未解决符号表,而不置入导出符号表。这属于外部链接。
编译器将static声明的全局变量不置入未解决符号表,也不置入导出符号表,因此其他单元无法使
用,这属于内部链接。
普通变量及其函数被置入导出符号表。
链接包含静态链接和动态链接。
(1)静态链接。
对函数库的链接是放在编译时期完成的是静态链接。这些函数库被称为静态库,通常为”libXXX.a“形
式。
如有5个文件:add.h,add.cpp,sub.h,sub.cpp,main.cpp
先将add.cpp,sub.cpp编译成.o文件
g++ -c add.cpp
g++ -c sub.cpp
无论是静态库文件还是动态库文件,都是由“.o”文件创建的。
由.o文件创建静态库(.a)文件,执行命令:
ar cr libmymath.a sub.o add.o
这样就会生成libmymath.a文件。其中lib是库文件的开头命名规范,mymyth是库名字,".a"说明是静态
库。
在后面指定了 -lmymath。这样g++会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名
来查找静态库文件。
(2)动态链接
动态链接用如下命令:
g++ -o main main.cpp -L. -lmypath (注意大写的L后面还有个“.”,表示当前目录)。
上面的链接是正常的,但是执行的时候回出错。
动态库搜索路径为;
(1)编译目标代码时指定的动态库搜索路径
(2)环境变量LD_LIBRARY_PATH指定的动态库搜索路径
(3)配置文件/etc/ld.so.conf中指定的动态库搜索路径;即只需在该文件中追加一行库所在的完整
路径如“root/test/conf/lib”即可;然后ldconfig是修改生效
(4)默认的动态库搜索路径/lib.
(5)默认的动态库搜索路径/usr/lib。
(3)动态库与静态库重名问题
当静态库文件和动态库文件同名的时候,编译器会先到path目录下搜索libXXX.so(动态库文件),如果没
有找到,这继续搜索libXXX.a(静态库文件);(先找动态库文件,若没找到,再找静态库文件)
(4)静态库链接,动态库链接各自的特点
(1)动态库链接有利于进程间资源共享。
(2)将一些程序升级变得简单。
(3)甚至可以真正做到链接载入完全由程序员在程序代码中控制
(4)静态库速度更快一些。
————————————————
版权声明:本文为CSDN博主「scut_yp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ypshowm/article/details/89374706