程序员必须了解的------编译与连接

在 Xcode 中按下 command + B
不出意外的话, 就会显示小锤子
小锤子下面是 Build Succeeded
Build 就是构建
其实构建是一个很复杂的过程
大致的分为 预处理 编译 汇编 链接 四个步骤
最近看了 程序员的自我修养 这本书和一些相关的博客
整理一下构建过程的细节


预编译

在构建的第一步是预编译
预编译做的事比较简单, 大概是:

  • 将所有的 #define 删除, 并且展开所有宏定义
  • 处理所有预编译指令, 比如 #if #ifdef #elif #else #endif
  • 处理 #include 预编译指令, 将被包含的文件插入到该预编译指令位置
  • 删除所有注释 /* */ //
  • 添加行号和文件标识, 以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时显示行号
  • 保留 #pragma 指令, 编译器会用到他们

编译

编译过程就是将预处理完的文件进行一系列操作 : 词法分析 语法分析 语义分析 优化并生成汇编代码
这个过程是构建程序最核心的部分, 也是最复杂的部分.

  • 词法分析

    首先源代码被输入到扫描器, 扫描器进行词法分析, 运用一种类似有限状态机的算法将源代码分割成记号
    例如这段代码 array[index] = index + 4 会被分割成这样 :
记号 类型
array 标识符
[ 左方括号
index 标识符
] 右方括号
= 赋值
( 左圆括号
index 标识符
+ 加号
4 数字
) 右圆括号
* 乘号
( 左圆括号
2 数字
+ 加号
6 数字
) 右圆括号

词法分析产生的记号一般分为以下几类 : 关键字 标识符 字面量(数字 字符串) 特殊符号(加号 等号),
与此同时, 扫描器还完成了其他工作 : 将标识符存放到符号表, 将数字字符串常量存放到文字表等.

  • 语法分析

    接下来语法分析器将对扫描器产生的记号进行语法分析, 从而产生语法树, 语法树是以表达式为节点的树 :

    语法树

    图中可以看出, 符号和数字是最小表达式, 他们不是有其他表达式组成的, 所以他们通常作为整个语法树的叶节点.
    在语法分析的同事, 很多运算符号的优先级也被定义下来, 比如乘法优先级比加法要高, 还有一些符号有多重含义, 比如*既可以代表乘法也可以代表对指针取内容, 语法分析阶段会对这些内容进行区分, 如果出现不合法的表达式, 编译器会报错.

  • 语义分析

    语义分析由语义分析器完成, 语法分析仅是对表达式的语法层面的分析, 但是它并不了解这个语义是否真正有含义, 比如 C 语言里面两个指针做乘法运算是没有意义的, 但是这个语法却是合法的.
    编译器能分析的是静态语义, 也就是能在编译期就确定的语义, 通常包括声明和类型的匹配, 类型的转换. 与之对应的是动态语义, 就是在运行期才能确定的语义, 比如在运行时将 0 作为除数是不合法的.
    经过语义分析后, 语法树的表达式被标识了类型 :


    语义分析后的语法树
  • 代码优化

现在的编译器有着很多层的优化, 往往在源码级就会有一个优化过程, 由源码级优化器完成, 比如 (2+6) 这个表达式, 在编译器就可以被确定, 生成如下语法树 :


优化后的语法树

其实直接在语法树上做优化比较困难, 所以源码优化器往往把整个语法树转换成中间代码, 他是语法树的顺序表示, 已经非常接近目标代码, 但是他一般跟目标机器和运行时环境是无关的, 比如他不包含数据的尺寸, 变量地址和寄存器的名字等. 中间代码使编译器被分为前端和后端, 编译器前端负责生产与机器无关的中间代码, 编译器后端将中间代码转换成目标代码. 这样对于一些可以跨平台的编译器而言, 他们可以针对不同的平台使用一个前端数个后端.

  • 目标代码生成

    源代码优化器产生中间代码后的过程属于编译器后端, 主要包括代码生成器和目标代码优化器.
    代码生成器将中间代码转换成目标机器代码, 这个过程十分依赖于目标机器, 因为不同的目标机器有着不同的字长, 寄存器, 整数数据类型和浮点数据类型等.
    目标机器代码再由目标代码优化器进行优化, 比如选择合适的寻址方式, 使用位移来代替运算, 删除多余指令等.

链接

经过 扫描 语法分析 语义分析 源代码优化 目标代码生成 目标代码优化 这一系列操作, 源码被编译成了目标代码, 但是目标代码有一个问题, index 和 array 的地址还没有确定, 如果 index 和 array 和源代码在同一个编译单元, 那么编译器可以为他们分配空间, 如果定义在别的程序模块就没办法了.

现在程序的代码规模往往很大, 所以每个程序会被分为多个模块, 这样做的好处是每个模块之间相互依赖又相互独立, 而且模块可以单独开发编译测试, 便于重用. 但是随之而来的问题就是模块之间怎么通信, 模块之间的通信包括函数的调用和变量的访问, 函数的访问需要知道函数的地址, 变量的访问需要知道变量的地址.

  • 模块拼装 --- 静态链接

    我们把每个源代码模块独立的编译, 然后将他们组装起来, 这个组装的过程就叫链接, 连接过程包括了地址分配, 符号决议和重定位.

    模块间的通信是地址的相互访问, 解决这个问题的方式就是模块间符号的引用, 模块中符号表分为已定义符号集合D,和一个未定义符合集合U, 未定的符号将引用其他模块中的符号.

    在连接的过程中, 每个模块会去其他模块中寻找自己未定义的那些符号的定义, 这个过程就是符号决议.

    在未找到符号符号之前, 模块先把这个未定义符号的地址置为 0, 当在其他模块中找到了该符号的定义的时候, 会重新给这个符号赋值地址, 这个过程就是重定位.

    静态链接的进本过程和作用 : 比如在程序 main.c 模块中使用另一个模块 func.c 中的函数 foo(). 我们再 main.c 模块中调用 foo 的时候必须知道 foo 这个函数的地址, 但是由于每个模块是单独编译的, main.c 编译的时候并不知道 foo 函数的地址, 所以他暂时把这个指令搁置(地址置 0), 等到最后连接的时候由连接器将指令的目标地址修正, 如果没有连接器, 我们需要手动修正 foo 的地址, 而且每次编译后地址可能会改变. 连接器在连接的时候会根据所引用的符号 foo, 自动取相应的 func.c 模块查找 foo 的地址, 然后将 main.c 模块中所引用的 foo 指令进行重定位,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容

  • CPU的工作分为 5 个阶段:取指令阶段、指令译码阶段、执行指令阶段、访存取数和结果写回。 1、取指令(IF,in...
    A_MARK阅读 3,074评论 0 2
  • 由于所在公司前端代码较不规范,近期应公司领导要求,整理出了一份公司内部的前端开发规范标准。这里参考了一些文章,并对...
    追寻1989阅读 1,163评论 0 3
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,813评论 0 27
  • # 基础概念 - 设计模式六大原则 设计模式六大原则](http://www.uml.org.cn/sjms/2...
    myr1782阅读 198评论 0 0
  • 寂静的夜里,开车回家,又是一个晚归的夜,脑袋里空空如也,没有想象力,只盼望早点到家。 想起明天不用上班,心情也是欢...
    吴长燃阅读 157评论 0 0