C++编译器、链接器工作原理
1 几个基本概念
编译:编译器对源文件的编译过程,就是将源文件中的文本形式代码翻译为机器语言形式的目标文件的过程,此过程中会有一系列语法检查、指令优化等,生成目标(OBJ)文件。
编译单元:每一个CPP文件就是一个编译单元,每个单元之间是互相独立且不可知的。
目标文件:编译步骤产生的文件,包含了编译单元内所有代码和数据,以二进制形式存在。请注意三个关键表:未解决符号表、导出符号表、地址重定向表。
链接:当编译器将工程中所有CPP文件以分离形式编译成各个对应目标(OBJ)文件之后,再由链接器进行链接生成一个EXE或者DLL文件。
2 一个例子
用一个例子来理解下编译器与链接器的工作:
file1.cpp
int gVal = ; void func1()
{
++gVal;
}
这个文件编译出来的目标文件file1.obj 就会有一个段来包含上面的数据和函数,内容大致如下(只是示意,并非完全一样):
偏移量 内容 长度
0x0000 gVal 4
0x0004 func1 ??
这里的??表示长度未知,实际目标文件的各个数据可能不是连续的,也不一定从0x0000开始。比如这里的func1可能是这样:
0x0004 inc DWORD PTR[0x0000]
0x00?? ret
这里把++gVal翻译为inc语句,也就是把本单元的0x0000地址的一个DWORD(4字节)进行加一。
file2.cpp
extern int gVal; void func2()
{
++gVal;
}
对应的file2.obj文件内容应该是:
偏移量 内容 长度
0x0000 func2 ??
可以看到这里并没有gVal,原因是extern关键字声明这是个外部引用符号,已经在别的单元里面定义了。由于单元之间是隔离的,所以这里的func2代码就没有办法填写地址,大致是这样:
0x0004 inc DWORD PTR[????]
0x00?? ret
这个????就表示当前无法拿到有效地址,需要链接器来完成,如何告诉链接器?需要一个未解决符号表(unresolved symbol table),同样提供gVal符号的目标文件也要提供一个导出符号表(export symbol table)来告知链接器可以提供的符号内容。
这两个表之间依靠符号来进行关联,在C/C++中每一个变量和函数都有自己的符号名,函数名略复杂(依据编译器不同而有差异),假设这里函数func1的符号为_func1,那么file1.obj文件的导出符号为:
导出符号 地址
gVal 0x0000
_func1 0x0004
而对应的未解决符号表则为空,因为没有依赖其他单元的内容。另一个file2.cpp文件的导出符号表为:
导出符号 地址
_func2 0x0000
而对应的未解决符号表为(下表的含义是说0x0001位置有一个地址不明,符号叫gVal):
未决符号 地址
gVal 0x0001
链接器会针对未解决符号表中的在所有导出符号表中进行匹配,如果找到则匹配填写进来,如果找不到就会报链接错误。但这里可以发现一个问题,就是gVal的符号地址是0x0000,如果直接将解析的地址替换未解决符表中的????就会生成一个冲突的地址0x0000,与本单元的地址重复了。为了解决这个问题,还需要针对每一个编译单元引入的一个地址偏移。例如file1.obj的地址从0x00001000开始,file2.obj的地址从0x00002000开始,这样符号地址叠加后就不会重复了。记录这样的地址偏移量的表称之为地址重定向表(address redirect table)。
3 链接器工作顺序
当链接器进行链接的时候,首先决定各个目标文件在最终可执行文件里的位置。然后访问所有目标文件的地址重定义表,对其中记录的地址进行重定向(加上一个偏移量,即该编译单元在可执行文件上的起始地址)。然后遍历所有目标文件的未解决符号表,并且在所有的导出符号表里查找匹配的符号,并在未解决符号表中所记录的位置上填写实现地址。最后把所有的目标文件的内容写在各自的位置上,再作一些另的工作,就生成一个可执行文件。
说明:实现链接的时候会更加复杂,一般实现的目标文件都会把数据,代码分成好向个区,重定向按区进行,但原理都是一样的。
extern:这就是告诉编译器,这个变量或函数在别的编译单元里定义了,也就是要把这个符号放到未解决符号表里面去(外部链接)。
static:如果该关键字位于全局函数或者变量的声明前面,表明该编译单元不导出这个函数或变量,因些这个符号不能在别的编译单元中使用(内部链接)。如果是static局部变量,则该变量的存储方式和全局变量一样,但是仍然不导出符号。
默认链接属性:对于函数和变量,默认链接是外部链接,对于const变量,默认内部链接。
外部链接:外部链接的符号在整个程序范围内都是可以使用的,这就要求其他编译单元不能导出相同的符号(不然就会报duplicated external symbols)。
内部链接:内部链接的符号不能在别的编译单元中使用。但不同的编译单元可以拥有同样的名称的符号。
为什么头文件里一般只可以有声明不能有定义:头文件可以被多个编译单元包含,如果头文件里面有定义的话,那么每个包含这头文件的编译单元都会对同一个符号进行定义,如果该符号为外部链接,则会导致duplicated external symbols链接错误。
为什么公共使用的内联函数要定义于头文件里:因为编译时编译单元之间互不知道,如果内联被定义于.cpp文件中,编译其他使用该函数的编译单元的时候没有办法找到函数的定义,因些无法对函数进行展开。所以如果内联函数定义于.cpp里,那么就只有这个.cpp文件能使用它。
C++编译器、链接器工作原理的更多相关文章
- 浅谈C++编译原理 ------ C++编译器与链接器工作原理
原文:https://blog.csdn.net/zyh821351004/article/details/46425823 第一篇: 首先是预编译,这一步可以粗略的认为只做了一件事情,那就 ...
- C++之编译器与链接器工作原理
原文来自:http://blog.sina.com.cn/s/blog_5f8817250100i3oz.html 这里并没不是讨论大学课程中所学的<编译原理>,只是写一些我自己对C++编 ...
- C++编译器与链接器工作原理
http://blog.csdn.net/success041000/article/details/6714195 1. 几个概念 1)编译:把源文件中的源代码翻译成机器语言,保存到目标文件中.如果 ...
- JVM(四) G1 收集器工作原理介绍
此篇文章半原创是对参考资料中的知识点进行总结,欢迎评论指点,谢谢! 部分知识点总结来自R大的帖子,下文有参考资料的链接 概述 G1 收集是相比于其他收集器(可见 上一篇文章),可以独立运 ...
- Struts自定义拦截器&拦截器工作原理
0.拦截器的调用原理: 拦截器是一个继承了序列化接口的普通接口.其工作原理是讲需要被拦截的对象作为参数传到intercept()方法内,在方法内部对此对象进行处理之后再执行原方法.intercept( ...
- 自己打断点走的struts流程&拦截器工作原理
①. 请求发送给 StrutsPrepareAndExecuteFilter ②. StrutsPrepareAndExecuteFilter 判定该请求是否是一个 Struts2 请 求(Actio ...
- 浏览器中“JavaScript解析器”工作原理
浏览器在读取HTML文件的时候,只有当遇到<script>标签的时候,才会唤醒所谓的“JavaScript解析器”开始工作. JavaScript解析器工作步骤: 1.“找一些东西”: v ...
- 20170103简单解析MySQL查询优化器工作原理
转自博客http://www.cnblogs.com/hellohell/p/5718238.html 感谢楼主的贡献 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL ...
- MySQL查询优化器工作原理解析
手册上查询优化器概述 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL的查询优化器,总或多或少地在所有可能的查询评估方案中搜索最佳方案.对于联接查询,MySQL优化器所 ...
随机推荐
- learning scala sealed class
package com.aura.scala.day01 object sealedClassed { def findPlaceToSit(piece: Furniture) = piece mat ...
- Qt进程间通信
Qt 提供了四种进程间通信的方式: 使用共享内存(shared memory)交互:这是 Qt 提供的一种各个平台均有支持的进程间交互的方式. TCP/IP:其基本思想就是将同一机器上面的两个进程一个 ...
- NetworkX系列教程(9)-线性代数相关
小书匠 Graph 图论 学过线性代数的都了解矩阵,在矩阵上的文章可做的很多,什么特征矩阵,单位矩阵等.grpah存储可以使用矩阵,比如graph的邻接矩阵,权重矩阵等,这节主要是在等到graph后 ...
- P1025 数的划分——简单题刷傻系列
P1025 数的划分 学傻了,学傻了,什么dp搜索什么啊: #include<cstdio> #include<cstring> #include<algorithm&g ...
- Mysql远程无法连接
#登陆mysql $ mysql -uroot -p mysql> use mysql; mysql> update user set host = '%' where user = 'r ...
- [linux]ubuntu修改镜像源
sudo apt-get update 更新源 sudo apt-get install package 安装包 sudo apt-get remove package 删除包 sudo apt-ca ...
- Poseidon 系统是一个日志搜索平台——认证看链接ppt,本质是索引的倒排列表和原始日志数据都存在HDFS,而文档和倒排的元数据都在NOSQL里,同时针对单个filed都使用了独立索引,使用MR来索引和搜索
Poseidon 系统是一个日志搜索平台,可以在百万亿条.100PB 大小的日志数据中快速分析和检索.360 公司是一个安全公司,在追踪 APT(高级持续威胁)事件,经常需要在海量的历史日志数据中检索 ...
- 微信小程序设置滚动条
前言 又很久没有写东西了,上周开始将一个APP和一个网站的内容整合到微信小程序中,到这会儿终于搞得快结束了,才发现为啥我的小程序滚动视图没有滚动条,这是闹哪样,没有滚动条的滚动是没有灵魂的. 客官可移 ...
- win10系统vs2008环境wince项目无法创建问题
文章备份,原文来自百度某个作者的博客. 昨晚,当我升级win10之后,发现系统使用还是挺顺畅的,没有当初升级win8的时候那么多错误. 但是今晚回来之后,发现之前win8.1下已经安装好的vs2008 ...
- 图片上传利用request.getInputStream()获取文件流时遇到的问题
图片上传功能是我们web里面经常用到的,获得的方式也有很多种,这里我用的是request.getInputStream()获取文件流的方式.想要获取文件流有两种方式,附上代码 int length = ...