| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 可执行文件的生成过程 -> 正文阅读 |
|
[C++知识库]可执行文件的生成过程 |
目录 一、假如你来发明编程语言1.机器语言(低级语言)????????0 和 1 编写指令,计算机能直接解读、运行。 2.汇编语言(低级语言)????????用一些容易理解和记忆的字母、单词来代替一个特定的指令。 3.高级语言????????高级计算机 语言便于人编写、阅读交流、维护。 (1)编译器????????编译器就是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序。 ????????一个现代编译器的主要工作流程:源代码 (source code) →预处理器??(preprocessor) → 编译器 (compiler) →??目标代码(object code) →??链接器(Linker) → 可执行程序?(executables)。 二、彻底理解链接器1.彻底理解链接器 - 前言 ?????????链接器是一个将编译器产生的目标文件打包成可执行文件或者库文件或者目标文件的程序。链接器的工作过程:
2.假如你来发明编程语言?(1)符号决议过程????????在这个过程当中,链接器需要做的工作就是确保所有目标文件中的符号引用都有唯一的定义。其实,一个目标文件由数据部分+代码部分+符号表组成。
????????本质上整个符号表只是想表达两件事:
????????假设链接器需要链接三个目标文件,链接器会依次扫描每一个给定的目标文件,同时链接器还维护了两个集合,一个是已定义符号集合D,另一个是未定义符合集合U,下面是链接器进行符合决议的过程:
3.彻底理解链接器 - 库与可执行文件的生成??(1)静态库????????不是所有静态库中的目标文件都会用到,而是用到哪个链接器就链接哪个。 ????????静态链接会将用到的目标文件直接合并到可执行文件当中,注意,如果有这样的一种静态库,几乎所有的程序都要使用到,也就是说,生成的所有可执行文件当中都有一份一模一样的代码和数据,这将是对硬盘和内存的极大浪费。
(2)动态库????????动态库允许使用该库的可执行文件仅仅包含对动态库的引用而无需将该库拷贝到可执行文件当中。也就是说,同静态库进行整体拷贝的方式不同,对于动态库的使用仅仅需要可执行文件当中包含必要的信息即可。 ????????动态链接可以在两种情况下被链接使用,分别是load-time dynamic linking(加载时动态链接) 以及 run-time dynamic linking(运行时动态链接)。 a.load-time dynamic linking(加载时动态链接) ????????在编译链接生成可执行文件的过程中要提供所依赖的动态库信息。 ????????加载指的是程序的加载,而所谓程序的加载就是把可执行文件从磁盘搬到内存的过程,因为程序最终都是在内存中被执行的。 ????????当把可执行文件复制到内存后,且在程序开始运行之前,操作系统会查找可执行文件依赖的动态库信息(主要是动态库的名字以及存放路径),找到该动态库后就将该动态库从磁盘搬到内存,并进行符号决议,如果这个过程没有问题,那么一切准备工作就绪,程序就可以开始执行了,如果找不到相应的动态库或者符号决议失败,那么会有相应的错误信息报告为用户,程序运行失败。 ????????从总体上看,加载时动态链接可以分为两个阶段:阶段一:将动态库信息写入可执行文件;阶段二:加载可执行文件时依据动态库信息进行动态链接。 2.run-time dynamic linking(运行时动态链接)? ????????在编译链接生成可执行文件的过程中没有提供所依赖的动态库信息,因此这项任务就留给了程序员,在代码当中如果需要使用某个动态库所提供的函数,我们可以使用特定的API来运行时加载动态库,在Windows下通过LoadLibrary或者LoadLibraryEx。 ????????在可执行文件被启动运行之前,可执行文件对所依赖的动态库信息一无所知,只有当程序运行到需要调用动态库所提供的代码时才会启动动态链接过程。 ????????在动态链接下,可执行文件当中会新增两段,即dynamic段以及GOT(Global offset table)段,这两段内容就是我们之前所说的必要信息。 ?????????dynamic段中保存了可执行文件依赖哪些动态库,动态链接符号表的位置以及重定位表的位置等信息。 (3)静态库VS动态库?????????在编译链接过程中,可以同时使用动态库以及静态库。这两种库的使用并不冲突,那么在这种情况下生成的可执行文件中,可执行文件中包含了静态库的数据和代码,以及动态库的必要信息。 静态库:
动态库:
4.彻底理解链接器 - 重定位??????????可执行文件中代码以及数据的运行时内存地址是链接器指定的。确定程序运行时地址的过程就是这里重定位(Relocation)。之所以叫做重定位是因为确定可执行文件中代码和数据的运行时地址是分为两个阶段的,在第一个阶段中无法确定这些地址,只有在第二个阶段才可以确定,因此就叫做重定位。 (1)第一个阶段:编译器的工作?????????源文件首先被编译器编译生成目标文件,目标文件种有三段内容:数据段、代码段以及符号表,所有的函数定义被放在了代码段,全局变量的定义放在了数据段,对外部变量的引用放到了符号表。 ?????????编译器在将源文件编译生成目标文件时可以确定一下两件事:
?????????注意这里的内存地址其实只是相对地址,相对于谁的呢,相对于自己的。因为在生成一个目标文件时编译器并不知道这个目标文件要和哪些目标文件进行链接生成最后的可执行文件,而链接器是知道要链接哪些目标文件的。因此编译器仅仅生成一个相对地址。 ?????????对于引用类的变量,也就是在当前代码中引用而定义是在其它源文件中的变量,对于这样的变量编译器是无法确定其内存地址的。也就是说对于编译器不能确定的地址都这设置为空(0x000000),同时编译器还会生成一条记录,该记录告诉链接器在进行链接时要修正这条指令中函数的内存地址,这个记录就放在了目标文件的.rel.text段中。相应的如果是对外部定义的全局变量的使用,则该记录放在了目标文件的.rel.data段中。即链接器需要在链接过程中根据.rel.data以及.rel.text来填好编译器留下的空白位置(0x000000)。 ?????????也就是说,生成目标文件后,编译器完成任务,编译器确定了定义在该源文件中函数以及全局变量的相对地址。对于编译器不能确定的引用类变量,编译器在目标文件的.rel.text以及.rel.data段中生成相应的记录告诉链接器要修正这些变量的地址。 (2)第二个阶段:链接器的工作a.重定位第一阶段 ?????????以静态库下可执行文件的生成为例,链接器会将所有的目标文件进行合并,所有目标文件的数据段合并到可执行文件的数据段,所有目标文件的代码段合并到可执行文件的代码段。当所有合并完成后,各个目标文件中的相对地址也就确定了。因此在这个阶段,链接器需要修正目标文件中的相对地址。 ?????????相对地址 + offset(偏移) = 最终内存地址 ?????????而每个段的偏移只有在链接完成后才能确定,因此对相对地址的修正只能由链接器来完成,编译器无法完成这项任务。 ?????????当所有目标文件的同类型段合并完毕后,数据段和代码段中的相对地址都被链接器修正为最终的内存位置,这样所有的变量以及函数都确定了其各自位置。 b.重定位第二阶段 ?????????我们知道编译器引用外部变量时将机器指令中的引用地址设置为空(比如call 0x000000),并将该信息记录在了目标文件的.rel.text以及.rel.data段中。因此在这个阶段链接器依次扫描所有的.rel.text以及.rel.data段并找到相应变量的最终地址(这些位置都已在第一阶段确定),并将机器指令中的0x000000修正为所引用变量的最终地址就可以了。 5.彻底理解链接器 - 大型项目是如何被构建出来的?(1)make自动化?????????如果某个源文件被修改了,也只需要简单的重新执行一下make命令,因为整个过程的规则并没有改变,而make也会很聪明的只编译链接那些需要更新的目标文件,库,并重新进行可执行文件的生成。对于那些没有改动的源文件,make不会重新编译它们。 三、程序员应如何理解include?1.头文件是被预编译器处理的????????预编译的工作非常简单,预编译器找到源文件中#include指定的文件,然后copy这些文件的内容并粘贴到#include这一行所在的位置。例如在源文件a.c的第一行有一句#include <stdio.h>,那么预编译器怎么处理?预编译找到stdio.h,把stdio.h的内容粘贴到a.c的第一行中。 ????????头文件是被预编译器处理的,编译器在编译源文件时拿到的是已经被预编译器处理过后的源文件,因此头文件是不会被编译器直接处理的。 ????????实际上#include可以出现在代码中的任意一行,只不过我们习惯了在开头使用#include,这是因为变量在声明之前是不能被使用的。 2.头文件引入格式????????预编译器要想处理头文件首先必须要能找到这个头文件。
????????头文件里仔仔细细的写好了该模块有哪些函数可供使用者调用、返回值是什么、参数是什么,但头文件中并不会包含实现,这是因为C/C++语言不要求函数的声明和实现必须呆在同一个地方。 四、程序员应如何理解标准库??????????C/C++以及任何一门编程语言都是这样的一堆规则,对于C/C++来说,每年都有一群来自被称为International Organization for Standardization (ISO)组织的人来制定C/C++语言的规则,因此这群人坐下来讨论的这堆规则实际上就是一个标准,每一次讨论都会重新修改制定新的标准并对外发布,这就是为什么C/C++有各种版本:C99, C11, C++03, C++11, C++14等等,其中的数字其实就是来自制定标准的年份。 ????????对外发布的标准中包含两部分内容:
????????从Visual Studio 2015之后,Windows中C/C++标准库被称为Universal C Runtime Library (Universal CRT,简称UCRT),即UCRTBASE.DLL,此后Windows标准库开始同Win10一起发布。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 23:40:18- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |