• 由代码到可执行程序----浅谈程序的编译链接


      本篇讨论研究C语言编译链接过程。

      我们先写一段最简单的C语言代码:

    1 #include <stdio.h>
    2 
    3 int main()
    4 {
    5     printf("Hello,World!
    ");
    6     return 0;
    7 }

       上诉过程可以分解为:预处理、编译、汇编、连接这四个阶段。

    一、预编译

      最开始我们写完的程序都是以hello.c文件形式储存在计算机磁盘上,并且还有相关的.h头文件。预编译器预编译成一个.i文件。对于C++程序来说,它的源代码文件拓展名可能时.cpp或.cxx,头文件也可能是.hpp,而预编译后的文件拓展名为.ii。

      在linux下的预编译指令为:

    $gcc  -E  hello.c  -o  hello.i

      预编译过程主要数处理那些源代码文件中的以“#”开始的预编译指令。比如“#include”、“#define”等,主要处理规则如下:

    ·将所有的“#define”删除,并且展开所有的宏定义

    ·处理所有条件预编译指令,比如“#if”,“#endif”...

    ·处理“#include”预编译指令,将被包含的头文件插入到该预编译指令的位置。同样递归处理头文件中的头文件。

    ·删除所有的注释“//”和“/* */”

    ·添加行号和文件名标识,比如#2“hello.c”,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号

    ·保留所有的#pragma编译指令,因为编译器须要使用他们。

    二、编译

      编译过程就是把预处理完的文件进行一系列的此法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。指令为:

    $gcc  -S  hello.i  -o  hello.s

    $gcc  -S  hello.c  -o  hello.s

       整个编译过程是程序构建的核心部分,也是最复杂的部分之一,其整个过程还分为扫描、此法分析、语法分析、语义分析、源代码优化、代码生成和目标代码优化。

      我们来看一段C语言代码:

      a[b] = (b + 5)*(9 + 7)

    1、词法分析

      首先源代码被输入到扫描器,扫描器运用有限状态机的算法将字符序列分割成一系列的记号。

      

      词法分析器产生的记号一般可以分为:关键字、标识符、字面量(数字、字符串等)和特殊数字(+、-、=等)。在识别记号的同时,扫描器也完成了其他工作。比如将标识符存放到符号表,将数字、字符串常量存放到文字表等,已备后面的步骤使用。

    2、语法分析

      语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树。整个分析过程采用上下无关文法的分析手段。

      

    3、语义分析

      语义分析器仅仅完成对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义。比如C语言里面两个指针做乘法运算时没有意义的,但是语句在语法上时合法的。编译器所能分析的语义是静态语义。

      静态语义:在编译期间可以确定的语义;

      动态语义:只有在运行期间才能确定的语义。

      经过语义分析阶段后,整个语法树的表达式都贝标识了类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。上面的语法树经过语义分析之后:

      可以看到,每个表达式(包括数字和字符)都被标志了类型。我们的例子几乎所有表达式都是整形的,所以无需转换,。语法分析器还对符号表里的符号类型也做了更新。

    4、代码优化

      现代编译器有很多层次的优化,我们说说在源代码级别的优化。源代码优化器在不同的编译器中也有不同定义或者一些其他的差异。而在上述的代码中,我们可以看到,(9+7)这个表达式可以被优化掉,因为它的值在编译器就可以确定。经过优化后的语法树为:

      

      我们看到(9+7)这个表达式被优化成了16。

      类似还有很多优化,同时直接在语法树上做优化比较困难,所以源代码优化器往往将整个语法树转化成中间代码,它非常接近目标代码,但是它一般跟目标机器和运行时环境是无关的,比如它不含数据的大小、变量地址和寄存器的名字等。

    三、汇编

      汇编器是将汇编代码转变成机器可以执行的指令,每一条汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程只是根据汇编指令和机器指令的对照表一一翻译就可以了,“汇编”这个名字也来源于此。

    $gcc  -c  hello.s  -o  hello.o

      或者使用gcc命令从C源代码文件开始,经过预编译、编译和汇编直接输出目标文件.o。

    $gcc  -c  hello.c  -o  hello.o

    四、链接

      当文件已经汇编成了目标文件.o之后,其实离可执行程序.exe文件只有一步之遥。

      现在的软件开发中,软件规模往往都很大,动辄数百万行代码,如果都放在一个模块中肯定无法想象。所以现在的大型软件都是拥有成千上万个模块,这些模块的拼接过程就是链接。链接的主要内容就是要把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。

      链接阶段分为:符号决议、地址空间分配、符号的重定位。

      链接也分为:静态链接、动态链接。

    五、总结

      经过链接之后的文件就是我们所说的可执行文件了,一张图结束。

      

  • 相关阅读:
    ajax原理及封装
    javascript 递归调用
    CSS的五种定位方式
    vue中iframe结合window.postMessage实现父子页面间的通信
    vue将文件流的形式的图形验证码转换成图片
    路由跳转打开新窗口,传递的参数不显示在地址栏
    js判断是否是ie浏览器、360浏览器兼容模式、QQ浏览器兼容模式、搜狗浏览器兼容模式,弹出提示使用别的浏览器打开
    vue项目中使用iframe嵌入静态页面,动态获取静态页面的高度赋值给iframe的高度
    vue项目中使用iview组件中的table插件实现表头文字居中,内容文字居左
    vue前端开发实现微信支付和支付宝支付
  • 原文地址:https://www.cnblogs.com/jian-99/p/7763810.html
Copyright © 2020-2023  润新知