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++编译器、链接器工作原理的更多相关文章

  1. 浅谈C++编译原理 ------ C++编译器与链接器工作原理

    原文:https://blog.csdn.net/zyh821351004/article/details/46425823 第一篇:      首先是预编译,这一步可以粗略的认为只做了一件事情,那就 ...

  2. C++之编译器与链接器工作原理

    原文来自:http://blog.sina.com.cn/s/blog_5f8817250100i3oz.html 这里并没不是讨论大学课程中所学的<编译原理>,只是写一些我自己对C++编 ...

  3. C++编译器与链接器工作原理

    http://blog.csdn.net/success041000/article/details/6714195 1. 几个概念 1)编译:把源文件中的源代码翻译成机器语言,保存到目标文件中.如果 ...

  4. JVM(四) G1 收集器工作原理介绍

    此篇文章半原创是对参考资料中的知识点进行总结,欢迎评论指点,谢谢!        部分知识点总结来自R大的帖子,下文有参考资料的链接 概述 G1 收集是相比于其他收集器(可见 上一篇文章),可以独立运 ...

  5. Struts自定义拦截器&拦截器工作原理

    0.拦截器的调用原理: 拦截器是一个继承了序列化接口的普通接口.其工作原理是讲需要被拦截的对象作为参数传到intercept()方法内,在方法内部对此对象进行处理之后再执行原方法.intercept( ...

  6. 自己打断点走的struts流程&拦截器工作原理

    ①. 请求发送给 StrutsPrepareAndExecuteFilter ②. StrutsPrepareAndExecuteFilter 判定该请求是否是一个 Struts2 请 求(Actio ...

  7. 浏览器中“JavaScript解析器”工作原理

    浏览器在读取HTML文件的时候,只有当遇到<script>标签的时候,才会唤醒所谓的“JavaScript解析器”开始工作. JavaScript解析器工作步骤: 1.“找一些东西”: v ...

  8. 20170103简单解析MySQL查询优化器工作原理

    转自博客http://www.cnblogs.com/hellohell/p/5718238.html 感谢楼主的贡献 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL ...

  9. MySQL查询优化器工作原理解析

    手册上查询优化器概述 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL的查询优化器,总或多或少地在所有可能的查询评估方案中搜索最佳方案.对于联接查询,MySQL优化器所 ...

随机推荐

  1. web+文件夹上传

    一. 大文件上传基础描述: 各种WEB框架中,对于浏览器上传文件的请求,都有自己的处理对象负责对Http MultiPart协议内容进行解析,并供开发人员调用请求的表单内容. 比如: Spring 框 ...

  2. [codevs]线段树练习5

    http://codevs.cn/problem/4927/ #include <iostream> #include <cstdio> #include <algori ...

  3. 块状链表 bzoj 3343教主的魔法

    //块状链表//分块排序,然后每次查找时在暴力查找头和尾两个块.//中间那些块,因为有序所以只需2分查找即可.我用的是lower_pound();//插入是,也是头和尾暴力插入,中间那些加到一个累计里 ...

  4. linux中清理旧内核

    执行update的时候会自动升级内核,开机启动的时候会好多内核选项.所以我们要清理不需要内核. 查看当前系统使用的内核版本 uname -a Linux localhost.localdomain 3 ...

  5. (转)libvirt和qemu编译安装

    借鉴:https://www.cnblogs.com/grglym/p/8053553.html 借鉴:http://blog.chinaunix.net/uid-31410005-id-577189 ...

  6. vue 脚手架安装

    首先安装node.js npm 配置全局安装路径和缓存 node 安装路径下新建两个目录,node_cache和node_global npm config set prefix "E:\n ...

  7. mkfs格式化分区(为分区写入文件系统)

    mkfs 命令非常简单易用,不过是不能调整分区的默认参数的(比如块大小是 4096 Bytes),这些默认参数除非特殊清况,否则不需要调整.如果想要调整,就需要使用 mke2fs 命令重新格式化.命令 ...

  8. windows系统下Jenkins 持续集成安装使用

    先要下载安装Tomcat,基于Java的web项目可以通过Tomcat运行.下载Jenkins,注意要安装在Tomcat的webapps目录下.安装完Jenkins会自动通过浏览器打开http://l ...

  9. TP框架修改后台路径方法

      直接映射 admin 后台修改路径为 myadmin888       文章来源:外星人来地球 欢迎关注,有问题一起学习欢迎留言.评论

  10. Random Projection

    Random Projection在k-means的应用   1. 随机投影 (Random Projection) 首先,这是一种降维方法.之前已经介绍过相对普遍的PCA的降维方法,这里介绍另一种降 ...