C/C++ - 从代码到可执行程序的过程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1预编译

主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下

  1. 删除所有的#define展开所有的宏定义。
  2. 处理所有的条件预编译指令如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
  3. 处理“#include”预编译指令将文件内容替换到它的位置这个过程是递归进行的文件中包含其
    他文件。
  4. 删除所有的注释“//”和“/**/”。
  5. 保留所有的#pragma 编译器指令编译器需要用到他们如#pragma once 是为了防止有文件
    被重复引用。
  6. 添加行号和文件标识便于编译时编译器产生调试用的行号信息和编译时产生编译错误或警告是
    能够显示行号。

2编译

把预编译之后生成的xxx.i或xxx.ii文件进行一系列词法分析、语法分析、语义分析及优化后生成相应
的汇编代码文件。

  1. 词法分析利用类似于“有限状态机”的算法将源代码程序输入到扫描机中将其中的字符序列分
    割成一系列的记号。
  2. 语法分析语法分析器对由扫描器产生的记号进行语法分析产生语法树。由语法分析器输出的
    语法树是一种以表达式为节点的树。
  3. 语义分析语法分析器只是完成了对表达式语法层面的分析语义分析器则对表达式是否有意义进
    行判断其分析的语义是静态语义——在编译期能分期的语义相对应的动态语义是在运行期才能
    确定的语义。
  4. 优化源代码级别的一个优化过程。
  5. 目标代码生成由代码生成器将中间代码转换成目标机器代码生成一系列的代码序列——汇编语
    言表示。
  6. 目标代码优化目标代码优化器对上述的目标机器代码进行优化寻找合适的寻址方式、使用位移
    来替代乘法运算、删除多余的指令等。

3汇编

将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单没
有复杂的语法也没有语义更不需要做指令优化只是根据汇编指令和机器指令的对照表一一翻译过
来汇编过程有汇编器as完成。经汇编之后产生目标文件(与可执行文件格式几乎一样)xxx.o(Windows 下)、xxx.obj(Linux下)。

4链接

将不同的源文件产生的目标文件进行链接从而形成一个可以执行的程序。链接分为静态链接和动态链

静态链接

函数和数据被编译进一个二进制文件。在使用静态库的情况下在编译链接可执行文件时链接器从库
中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。

以下面这个图来简单说明一下从静态链接到可执行文件的过程根据在源文件中包含的头文件和程序中使用到的库函数如stdio.h中定义的printf()函数在libc.a中找到目标文件printf.o(这里暂且不考虑printf()函数的依赖关系)然后将这个目标文件和我们hello.o这个文件进行链接形成我们的可执行文件。
在这里插入图片描述
这里有一个小问题就是从上面的图中可以看到静态运行库里面的一个目标文件只包含一个函数如libc.a里面的printf.o只有printf()函数strlen.o里面只有strlen()函数。

我们知道链接器在链接静态链接库的时候是以目标文件为单位的。比如我们引用了静态库中的printf()函数那么链接器就会把库中包含printf()函数的那个目标文件链接进来如果很多函数都放在一个目标文件中很可能很多没用的函数都被一起链接进了输出结果中。由于运行库有成百上千个函数数量非常庞大每个函数独立地放在一个目标文件中可以尽量减少空间的浪费那些没有被用到的目标文件就不要链接到最终的输出文件中。

缺点
空间浪费因为每个可执行程序中对所有需要的目标文件都要有一份副本所以如果多个程序对同一个
目标文件都有依赖会出现同一个目标文件都在内存存在多个副本
更新困难每当库函数的代码修改了这个时候就需要重新进行编译链接形成可执行程序。
运行速度快但是静态链接的优点就是在可执行程序中已经具备了所有执行程序所需要的任何东西
在执行的时候运行速度快。

动态链接

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。

假设现在有两个程序program1.o和program2.o这两者共用同一个库lib.o,假设首先运行程序program1系统首先加载program1.o当系统发现program1.o中用到了lib.o即program1.o依赖于lib.o那么系统接着加载lib.o如果program1.o和lib.o还依赖于其他目标文件则依次全部加载到内存中。当program2运行时同样的加载program2.o然后发现program2.o依赖于lib.o但是此时lib.o已经存在于内存中这个时候就不再进行重新加载而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中从而进行链接这个链接过程和静态链接类似形成可执行程序。

优点
共享库就是即使需要每个程序都依赖同一个库但是该库不会像静态链接那样在内存中存在多分副
本而是这多个程序在执行时共享同一份副本更新方便更新时只需要替换原来的目标文件而无需将所有的程序再重新链接一遍。当程序下一次运行时新版本的目标文件会被自动加载到内存并且链接起来程序就完成了升级的目标。
性能损耗因为把链接推迟到了程序运行时所以每次执行程序都需要进行链接所以性能会有一定损失。

生成可执行文件

什么情况会编译成功但链接失败

(1)说明并使用了类型、函数、变量但没给出相应类型、函数或变量的定义。关于说明和定义的区别见上述教程说明可以通过#include头文件进行也可以直接进行说明如int f( )或extern int f( )。若同时给出函数f的函数体则称为定义。对于变量若有初始值就算定义例如"extern int x=3; "便是定义这种语法有其独特应用背景。

(2)对标准库的连接失败例如调用了sin(double x)却找不到数学运算标准库进行连接了可能是安装后因各种可能原因被删除了。

(3)全局变量和静态变量存放在数据段而固定大小的数据段不够用了例如定义太多的全局变量和静态变量甚至巨型数组全局或静态变量。

(4)数据段被偷用后无法容纳较多的全局变量和静态变量这种错误最难发现且最难改正。主要是一些常量在偷用数据段例如字符串常量"abc"等等其它诸多情形参见上述教程。另外虚函数入口地址表等也会偷用。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++