C++编译连接详解
C++程序从编译到链接然后再到调用的整个过程如下:注:这里只是研究C++的主流编译过程,与Java没有任何关系,因为使用的技术完全不一样(Java是编译和解释结合的语言)。
并且由于不同的编译器厂商对于程序的编译过程不尽相同,但是主要流程还是一样的。
其实长久以来我就一直很不清楚obj文件的内容到底是什么,有人说是汇编,有人说是机器语言。
如果是机器语言的话,那编译的过程是怎样加入操作系统信息的呢?因为这个问题的不断扩展和困扰,便决定彻底研究一下,网上几乎找不到相关资料,作者参照了基本系统编程的书籍后自行整理而来,数目见底,仅供参考,欢迎讨论。
一个C++工程中会存在cpp文件,头文件,库文件。
1.首先经历的是预处理过程,将头文件加载进来,并且将各种#define信息代入。
这时会见不到头文件,工程经过处理后会生成以cpp文件为基础的编译单元。
有人可能会问那么头文件到哪里去了。
其实头文件将cpp文件中的#include替换掉了。
因此在以后的编程中需要严格注意include的先后顺序。
因为C++语言是一种很注重申明的语言,为什么会这样这与程序的编译过程和链接过程的算法有关。
貌似话题有点转远了,其实在这个阶段是生成一个个独力的编译单元。
2.在编译单元生成之后,便是将编译单元进行编译,其实对于主流的编译其实存在两个阶段,首先是生成汇编语言,然后使用汇编器生成机器语言。
其实这里要讲解的是汇编语言怎么变成机器语言的呢。
机器语言顾名思义就是0101的二进制代码。
对于一个类似于MOV AX,BX(这里写的是Intel 80x86的汇编代码,其实几乎每一种不同架构的芯片的汇编语言不怎么一样)的代码而言就是将MOV和AX和BX原封不动的用0101替换掉,如MOV 代码是35的话AX为01,BX为10的话翻译的机器代码就是350110,二进制也就是001101010000000100010000。
3.接下来的任务是链接。
链接的过程如下所示:因为篇幅太长,请看附件。
其实链接的任务是生成可执行文件。
其实我的一些不确认也就在这个地方。
其实每一个程序都肯定有操作系统的一些信息,比如说程序的运行环境是DOS还是Windows程序,程序的大小等。
我认为编译的整个过程中应该是在最后生成可执行文件的时候加入的。
以上便是对于编译,链接的整个过程。
个人意见,仅作参考。
参考资料:1. 《Thinking in C++》 Bruce Eckel 机械工业出版社2. 《高级语言程序设计》谭浩强清华大学出版社3. 《计算机组成结构化方法》 Andrew S. Tanenbaum 机械工业出版社4. 《计算机组成与设计硬件/软件接口》 David A. Patterson John L. Hennessy 机械工业出版社附件:链接器的使用许多 Visual C++ 的使用者都碰到过 LNK2005:symbol already defined 和 LNK1169:one or more multiply defined symbols found 这样的链接错误,而且通常是在使用第三方库时遇到的。
对于这个问题,有的朋友可能不知其然,而有的朋友可能知其然却不知其所以然,那么本文就试图为大家彻底解开关于它的种种疑惑。
大家都知道,从 C/C++ 源程序到可执行文件要经历两个阶段 :(1) 编译器将源文件编译成汇编代码,然后由汇编器(assembler) 翻译成机器指令 ( 再加上其它相关信息 ) 后输出到一个个目标文件 (object file, VC 的编译器编译出的目标文件默认的后缀名是 .obj) 中;(2) 链接器 (linker) 将一个个的目标文件 ( 或许还会有若干程序库 ) 链接在一起生成一个完整的可执行文件。
编译器编译源文件时会把源文件的全局符号(global symbol) 分成强 (strong) 和弱 (weak) 两类传给汇编器,而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。
那么何谓强弱呢?编译器认为函数与初始化了的全局变量都是强符号,而未初始化的全局变量则成了弱符号。
比如有这么个源文件 :extern int errorno;int buf[2] = {1,2};int *p;int main(){return 0;}其中 main 、 buf 是强符号, p 是弱符号,而 errorno 则非强非弱,因为它只是个外部变量的使用声明。
有了强弱符号的概念,我们就可以看看链接器是如何处理与选择被多次定义过的全局符号 :规则 1: 不允许强符号被多次定义 ( 即不同的目标文件中不能有同名的强符号 ) ;规则 2: 如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号;规则 3: 如果一个符号在所有目标文件中都是弱符号,那么选择其中任意一个;由上可知多个目标文件不能重复定义同名的函数与初始化了的全局变量,否则必然导致 LNK2005 和 LNK1169 两种链接错误。
可是,有的时候我们并没有在自己的程序中发现这样的重定义现象,却也遇到了此种链接错误,这又是何解?嗯,问题稍微有点儿复杂,容我慢慢道来。
众所周知, ANSI C/C++ 定义了相当多的标准函数,而它们又分布在许多不同的目标文件中,如果直接以目标文件的形式提供给程序员使用的话,就需要他们确切地知道哪个函数存在于哪个目标文件中,并且在链接时显式地指定目标文件名才能成功地生成可执行文件,显然这是一个巨大的负担。
所以 C 语言提供了一种将多个目标文件打包成一个文件的机制,这就是静态程序库 (static library) 。
开发者在链接时只需指定程序库的文件名,链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块,并把 ( 且只把 ) 它们从库中拷贝出来参与构建可执行文件。
几乎所有的 C/C++ 开发系统都会把标准函数打包成标准库提供给开发者使用。
程序库为开发者带来了方便,但同时也是某些混乱的根源。
我们来看看链接器是如何解析 (resolve) 对程序库的引用的。
在符号解析 (symbol resolution) 阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合 :(1) 集合 E 是将被合并到一起组成可执行文件的所有目标文件集合;(2) 集合 U 是未解析符号 (unresolved symbols ,比如已经被引用但是还未被定义的符号 ) 的集合;(3) 集合 D 是所有之前已被加入到 E 的目标文件定义的符号集合。
一开始, E 、 U 、 D 都是空的。
链接器的工作过程:(1): 对命令行中的每一个输入文件 f ,链接器确定它是目标文件还是库文件,如果它是目标文件,就把 f 加入到 E ,并把 f 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中,然后处理下一个输入文件。
(2): 如果 f 是一个库文件,链接器会尝试把 U 中的所有未解析符号与 f 中各目标模块定义的符号进行匹配。
如果某个目标模块 m 定义了一个 U 中的未解析符号,那么就把 m 加入到 E 中,并把 m 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中。
不断地对 f 中的所有目标模块重复这个过程直至到达一个不动点 (fixed point) ,此时 U 和 D 不再变化。
而那些未加入到 E 中的 f 里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。
(3): 如果处理过程中往 D 加入一个已存在的符号,或者当扫描完所有输入文件时 U 非空,链接器报错并停止动作。
否则,它把 E 中的所有目标文件合并在一起生成可执行文件。
VC 带的编译器名字叫 cl.exe ,它有这么几个与标准程序库有关的选项 : /ML 、 /MLd 、 /MT 、 /MTd 、 /MD 、 /MDd 。
这些选项告诉编译器应用程序想使用什么版本的 C 标准程序库。
/ML( 缺省选项) 对应单线程静态版的标准程序库(libc.lib) ; /MT 对应多线程静态版标准库 (libcmt.lib) ,此时编译器会自动定义 _MT 宏; /MD 对应多线程 DLL 版 ( 导入库 msvcrt.lib , DLL 是 msvcrt.dll) ,编译器自动定义_MT 和 _DLL 两个宏。
后面加 d 的选项都会让编译器自动多定义一个 _DEBUG 宏,表示要使用对应标准库的调试版,因此 /MLd 对应调试版单线程静态标准库 (libcd.lib) , /MTd 对应调试版多线程静态标准库 (libcmtd.lib) , /MDd 对应调试版多线程DLL 标准库( 导入库msvcrtd.lib ,DLL 是msvcrtd.dll) 。
虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库,可是当编译器干完了活,轮到链接器开工时它又如何得知一个个目标文件到底在思念谁?为了传递相思,我们的编译器就干了点秘密的勾当。
在 cl 编译出的目标文件中会有一个专门的区域 ( 关心这个区域到底在文件中什么地方的朋友可以参考 COFF 和 PE 文件格式 ) 存放一些指导链接器如何工作的信息,其中有一种就叫缺省库(default library) ,这些信息指定了一个或多个库文件名,告诉链接器在扫描的时候也把它们加入到输入文件列表中 ( 当然顺序位于在命令行中被指定的输入文件之后 ) 。
说到这里,我们先来做个小实验。
写个顶顶简单的程序,然后保存为 main.c :/* main.c */int main() { return 0; }用下面这个命令编译 main.c( 什么?你从不用命令行来编译程序?这个 ......) :cl /c main.c/c 是告诉 cl 只编译源文件,不用链接。
因为 /ML 是缺省选项,所以上述命令也相当于 : cl /c /ML main.c 。
如果没什么问题的话 ( 要出了问题才是活见鬼!当然除非你的环境变量没有设置好,这时你应该去 VC 的 bin 目录下找到 vcvars32.bat 文件然后运行它。
) ,当前目录下会出现一个 main.obj 文件,这就是我们可爱的目标文件。
随便用一个文本编辑器打开它( 是的,文本编辑器,大胆地去做别害怕 ) ,搜索 "defaultlib" 字符串,通常你就会看到这样的东西 : "-defaultlib:LIBC-defaultlib:OLDNAMES" 。
啊哈,没错,这就是保存在目标文件中的缺省库信息。
我们的目标文件显然指定了两个缺省库,一个是单线程静态版标准库 libc.lib( 这与 /ML 选项相符 ) ,另外一个是 oldnames.lib( 它是为了兼容微软以前的 C/C++ 开发系统 ) 。
c语言预处理编译汇编链接
c语言预处理编译汇编链接【原创版】目录1.C 语言预处理2.编译3.汇编4.链接正文C 语言预处理、编译、汇编和链接是编写 C 语言程序的重要步骤。
下面将详细介绍这四个步骤。
1.C 语言预处理C 语言预处理器负责处理源代码中的宏定义、头文件包含和条件编译等指令。
在编译之前,预处理器会读取源代码,并将这些指令替换为相应的代码。
例如,预处理器会将`#define PI 3.14159`替换为`PI 3.14159`,将`#include <stdio.h>`替换为`#include stdio.h`。
2.编译编译是将 C 语言源代码转换为目标语言(通常是汇编语言)的过程。
编译器负责这个任务。
它会检查源代码中的语法错误,并将其转换为目标语言。
例如,编译器将`int main() { printf("Hello, world!"); }`转换为`main: pushl hello world`(汇编语言)。
3.汇编汇编是将汇编语言代码转换为机器语言的过程。
汇编器负责这个任务。
它会将汇编语言代码转换为二进制代码,以便计算机可以执行。
例如,汇编器将`main: pushl hello world`转换为`0000000000000000 0110100101101010 0110101001101011 0110101001101010`(二进制代码)。
4.链接链接是将编译和汇编后的目标文件合并为一个可执行文件的过程。
链接器负责这个任务。
它会将各个目标文件中的代码和数据段合并为一个完整的程序。
例如,链接器将`main.o`(编译后的目标文件)和`printf.o`(汇编后的目标文件)合并为`main.out`(可执行文件)。
总之,C 语言预处理、编译、汇编和链接是编写 C 语言程序的重要环节。
c语言编译链接过程
C语言编译链接过程主要包括预处理、编译、汇编和链接四个阶段。
1. 预处理:预处理阶段主要处理以"#"开头的预处理指令,如宏定义、头文件包含等。
预处理器会根据这些指令对源代码进行处理,生成一个没有宏定义、没有注释、没有条件编译指令的纯C语言代码文件。
2. 编译:编译阶段将预处理后的代码文件转换为汇编代码。
编译器会对代码进行词法分析、语法分析和语义分析,生成相应的中间代码。
3. 汇编:汇编阶段将汇编代码转换为机器码。
汇编器会将汇编代码转换为可执行的机器指令,并生成目标文件。
4. 链接:链接阶段将目标文件和库文件进行链接,生成最终的可执行文件。
链接器会将目标文件中的符号引用与库文件中的符号定义进行匹配,解析符号引用,生成最终的可执行文件。
在链接过程中,还会进行地址重定位、符号解析、符号重定位等操作,以确保最终生成的可执行文件能够正确运行。
需要注意的是,编译链接过程可以分为静态链接和动态链接两种方式。
静态链接是将所有的目标文件和库文件都链接到最终的可执行文件中,而动态链接是在运行时将依赖的库文件加载到内存中。
C语言编译过程总结详解
C语言编译过程总结详解C语言的编译过程可以分为四个主要阶段:预处理、编译、汇编和链接。
下面会详细解释每个阶段的工作原理。
1.预处理阶段:预处理器的主要作用是根据源文件中的预处理指令对源代码进行一系列的文本替换和宏展开,生成经过预处理的源代码文件。
预处理指令以"#"开头,主要包括#include、#define、#ifdef等。
预处理器的工作原理如下:- 处理#include指令:将包含的头文件内容插入到当前位置,形成一个单独的源代码文件。
- 处理#define指令:将宏定义替换为对应的内容。
- 处理#ifdef指令:根据条件判断指令是否执行。
预处理阶段生成的文件以".i"为后缀,可以用编译器提供的预处理器命令进行预处理,如gcc -E source.c -o source.i。
2.编译阶段:编译器将预处理阶段生成的经过预处理的源文件进行词法分析、语法分析、语义分析和优化,生成汇编代码。
编译阶段包括以下几个步骤:-词法分析:将源代码分解成一个个的词法单元,如标识符、关键字、常量等。
-语法分析:分析和验证词法单元之间的语法关系,生成语法树。
-语义分析:对语法树进行语义检查,如类型检查、变量声明检查等。
-优化:进行编译优化,如常量折叠、无用代码删除等。
编译阶段生成的文件以".s"为后缀,可以用编译器提供的编译器命令将汇编代码转化为可执行文件,如gcc -S source.i -o source.s。
3.汇编阶段:汇编器将编译阶段生成的汇编代码转化为机器码。
汇编阶段包括以下几个步骤:-符号解析:将符号(如函数名、变量名)与其对应的地址进行关联。
-指令生成:将汇编代码转化为机器码。
汇编阶段生成的文件以".o"为后缀,可以用编译器提供的汇编器命令将目标文件转化为可执行文件,如gcc -c source.s -o source.o。
C语言编译过程详解
C语言编译过程详解C语言是一种高级编程语言,它使用简洁的语法和强大的功能,被广泛应用于各种软件开发领域。
然而,在我们编写C语言代码后,计算机并不能直接理解和执行它们。
相反,我们需要通过编译器将C语言代码转换为机器语言,以便计算机能够正确地运行程序。
这个过程被称为C语言的编译过程,本文将对其进行详细解析。
1. 词法分析在编译过程的第一阶段,编译器将源代码中的字符序列分解为单个的词素(Token)。
词素可以是关键字、标识符、运算符、常量或者其他类型的符号。
编译器会根据事先定义好的语法规则,将源代码按照词素进行划分,并生成词法单元序列。
2. 语法分析词法单元序列被传递给语法分析器,它根据语法规则构建出语法分析树(Syntax Tree)。
语法分析树反映了源代码的层级结构和语法关系。
编译器会检查代码的语法是否合法,并对其进行语义分析。
3. 语义分析在语义分析阶段,编译器会进一步检查代码中的上下文信息,以确保程序的语义正确。
这一阶段会处理变量的声明、类型推断、函数调用等操作,并生成中间代码表示形式。
4. 代码生成在代码生成阶段,编译器将中间代码转换为目标机器代码。
这个过程通常分为多个步骤,包括指令选择、寄存器分配、代码优化等。
最终生成可执行文件或目标文件,以供后续的执行或链接操作使用。
5. 代码优化代码优化是编译过程中的重要环节。
它的目标是通过改进代码的执行效率、减少代码的大小以及提高程序的性能。
常见的代码优化技术包括常量合并、循环展开、代码复用等。
6. 链接在一些大型项目中,源代码可能会分为多个文件进行编写,这就需要通过链接器将这些文件整合成可执行文件。
链接的过程包括地址分配、符号解析、重定位等。
最终生成可以被操作系统加载和执行的可执行文件。
总结:C语言编译过程可以分为词法分析、语法分析、语义分析、代码生成、代码优化和链接等阶段。
编译器会通过对源代码的解析和转换,最终生成可执行文件或目标文件。
编译过程中的代码优化环节能够提升程序的执行效率和性能。
c语言编译的链接文件解析
c语言编译的链接文件解析C语言编译的链接文件解析一、引言在C语言编程中,链接是将多个目标文件合并为一个可执行文件的过程。
链接文件的作用是将程序中的各个模块进行组合,解决模块之间的引用关系,使得程序能够正确地运行。
本文将介绍C语言编译的链接文件解析的相关知识。
二、链接的基本概念链接是将编译器生成的目标文件与库文件进行合并,生成可执行文件的过程。
链接分为静态链接和动态链接两种方式。
1. 静态链接静态链接是将所有的目标文件和库文件的代码和数据合并到一个可执行文件中。
在静态链接的过程中,连接器将目标文件中的符号引用与符号定义进行匹配,将符号引用替换为符号定义的地址,最终生成可执行文件。
静态链接的优点是生成的可执行文件独立存在,不依赖于其他文件;缺点是可执行文件的体积比较大。
2. 动态链接动态链接是在运行时将目标文件和库文件的代码和数据加载到内存中,生成可执行文件的过程。
在动态链接的过程中,连接器只处理符号引用与符号定义的匹配关系,生成一个包含符号引用的表格。
在程序运行时,操作系统根据这个表格将符号引用替换为符号定义的地址。
动态链接的优点是可执行文件的体积小,共享库可以被多个程序共享;缺点是程序依赖于共享库,如果共享库缺失或版本不兼容,程序将无法运行。
三、链接文件的结构链接文件一般包含以下几个部分:1. 文件头(File Header):记录了链接文件的一些基本信息,如文件的魔数、版本号等。
2. 段表(Section Header Table):记录了链接文件中各个段的信息,如段的起始地址、大小等。
3. 符号表(Symbol Table):记录了链接文件中定义和引用的符号的信息,如符号的名称、类型、地址等。
4. 重定位表(Relocation Table):记录了链接文件中需要进行重定位的位置和相关信息,用于将目标文件中的符号引用替换为符号定义的地址。
5. 字符串表(String Table):记录了链接文件中使用的字符串,如符号的名称、段的名称等。
C语言编译过程总结详解
C语言编译过程总结详解C语言编译过程总结详解链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。
编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。
链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。
过程图解如下:从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。
编译过程编译过程又可以分成两个阶段:编译和会汇编。
编译编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:第一个阶段是预处理阶段,在正式的编译阶段之前进行。
预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。
如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。
这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。
一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。
在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。
主要是以下几方面的处理:(1)宏定义指令,如 #define a b对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。
还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。
预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
(3) 头文件包含指令,如#include "FileName"或者#include 等。
c语言编译过程详解
C语言编译过程通常分为预处理、编译、汇编和链接四个步骤。
以下是C语言编译过程的详细解释:
1. 预处理:在编译之前,预处理器会对源代码进行预处理。
预处理包括以下步骤:
-删除源代码中的注释
-展开宏定义
-处理文件中的预定义符号
2. 编译:编译器将预处理后的代码转换成中间代码(即汇编语言)。
编译器会对源代码进行词法分析、语法分析和优化,生成目标代码(即汇编语言)。
3. 汇编:汇编器将汇编代码转换成机器指令。
汇编器将汇编指令转换成机器指令,并将它们组合成可执行的程序。
4. 链接:链接器将多个目标文件组合成一个可执行文件或共享库文件。
链接器会解决符号引用问题,并将它们链接到相应的代码段和数据段。
在C语言编译过程中,编译器和链接器通常使用标准库和用户定义的库。
标准库提供了一些常用的函数和数据类型,如printf()和malloc()。
用户定义的库可以包含自定义的函数和数据类型,以便更好地满足应用程序的需求。
总之,C语言编译过程是一个复杂的过程,需要多个步骤和工具的协同工作。
正确的编译过程可以确保生成的可执行程序具有良好的性能和可靠性。
链接及编译详解
链接及编译详解这个问题没有具体指定要解释的是哪个链接及编译,所以我将给出一个简单的解释和示例来解释链接和编译的概念。
链接和编译是软件开发过程中的两个重要步骤。
编译是将程序源代码转换为计算机可以理解的机器代码的过程,链接是将各个模块的机器代码合并成一个可执行的程序。
编译过程通常包括以下几个步骤:1. 预处理:处理源代码中的宏定义、条件编译等预编译指令,生成经过预处理的源代码。
2. 编译:将预处理后的源代码转换为汇编语言或机器语言的过程。
3. 汇编:将汇编语言翻译为机器语言的过程。
4. 链接:将多个目标文件合并成一个可执行文件的过程。
链接可以分为静态链接和动态链接两种方式:- 静态链接:在编译阶段将所有需要的代码和库文件链接到可执行文件中。
静态链接生成的可执行文件相对较大,但执行速度较快,不需要依赖外部库文件。
- 动态链接:在编译阶段只链接所需的库文件的引用,而不将其代码复制到可执行文件中。
在运行时,操作系统会在内存中加载所需的库文件。
动态链接生成的可执行文件相对较小,但执行速度稍慢,需要依赖外部库文件。
以下是一个简单的C语言程序的编译和链接示例:假设我们有一个文件名为hello.c的源代码文件,内容如下:```c#include<stdio.h>int main() {printf("Hello, World!");return 0;}```我们可以用以下命令将其编译和链接成可执行文件:```gcc -o hello hello.c```其中,gcc是C语言编译器,-o选项指定生成的可执行文件名为hello,hello.c是源代码文件的路径。
编译过程会生成一个名为hello.o的目标文件,然后链接器将目标文件与所需的库文件(如stdio.h)进行链接,最终生成一个名为hello的可执行文件。
通过运行./hello命令,我们可以在命令行中看到程序输出的"Hello, World!"。
C语言程序的编译和链接过程
C语⾔程序的编译和链接过程C语⾔程序的编译和链接过程1.程序的编译⼀般⽽⾔,⼤多数编译系统都提供编译驱动程序(complier driver),根据⽤户需求调⽤语⾔预处理器,编译器,汇编器和链接器.例如有如下历程: //main.cvoid swap();int buf[2]={1, 2};int main(){swap();return 0;}//swap.cint *bufp0 = &buf[0]int *bufp1;void swap(){int temp;bufp1 = &buf[1];temp = *bufp0;*bufp0 = *bufp1;*bufp1 = temp;}驱动程序⾸先运⾏C预处理器(cpp),它将C的源程序main.c翻译成⼀个ASCII码的中间⽂件main.i.接下来,驱动程序运⾏C编译器(ccl),将main.i 翻译成⼀个ASCII汇编语⾔⽂件main.s.然后,驱动程序运⾏汇编器(as),它将main.s翻译成⼀个可重定位的⽬标⽂件main.o.具体过程如下图所⽰:2.链接链接就是将不同部分的代码和数据收集和组合成为⼀个单⼀⽂件的过程,这个⽂件可被加载或拷贝到存储器执⾏.链接可以执⾏与编译时(源代码被翻译成机器代码时),也可以执⾏与加载时(在程序被加载器加载到存储器并执⾏时),甚⾄执⾏与运⾏时,由应⽤程序来执⾏.在现代系统中,链接是由链接器⾃动执⾏的.链接器分为:静态链接器和动态链接器两种.2.1.静态链接器静态链接器以⼀组可重定位⽬标⽂件和命令⾏参数作为输⼊,⽣成⼀个完全链接的可以加载和运⾏的可执⾏⽬标⽂件作为输出.静态链接器主要完成两个任务:1>符号解析:⽬标⽂件定义和引⽤符号.符号解析的⽬的在于将每个符号引⽤和⼀个符号定义联系起来.2>重定位:编译器和汇编器⽣成从地址零开始的代码和数据节.链接器通过把每个符号定义和⼀个存储器位置联系起来,然后修改所有对这些符号的引⽤,使得他们执⾏这个存储位置,从⽽重定位这些节.⽬标⽂件:⽬标⽂件有三种形式:1>可重定位的⽬标⽂件:包含⼆进制代码和数据,其形式可以再编译时与其他可定位⽬标⽂件合并起来,创建⼀个可执⾏⽬标⽂件.2>可执⾏⽬标⽂件:包含⼆进制代码和数据,其形式可以被直接拷贝到存储器并执⾏.3>共享⽬标⽂件:⼀种特殊的可重定位⽬标⽂件,可以再加载或运⾏时,被动态地夹在到存储器并执⾏.编译器和汇编器⽣成可重定位⽬标⽂件(包括共享⽬标⽂件),链接器⽣成可执⾏⽬标⽂件.可重定位⽬标⽂件:EF头L以⼀个16字节的序列开始,这个序列描述了字的⼤⼩和⽣成该⽂件的系统字节顺序.ELF头剩下的部分包含帮助链接器解析和解释⽬标⽂件的信息.其中包括ELF头的⼤⼩,⽬标⽂件的类型(⽐如,可重定位,可执⾏,共享⽬标⽂件),机器类型,节头部表的⽂件偏移,以及节头部表中的表⽬⼤⼩和数量.不同节的位置和⼤⼩是节头部表描述的,其中⽬标⽂件中的每个节都有⼀个固定⼤⼩的表⽬.ELF格式的可重定位⽬标⽂件结构如下图:.text:已编译程序的机器代码.rodata:只读数据.data:已初始化的全局C变量.bss:未初始化的全局C变量.在⽬标⽂件中这个节不占实际空间,仅是⼀个占位符..sysmtab:⼀个符号表,存放在程序中被定义和引⽤的函数和全局变量的信息..rel.text:当链接器把这个⽬标⽂件和其他⽂件结合时,.text节中的许多位置都需要修改.⼀般⽽⾔,任何调⽤外部函数或者引⽤全局变量的指令都要修改.另⼀个⽅⾯,调⽤本地函数的指令则不需要修改..rel.data:被模块定义或引⽤的任何全局变量的信息..debug:⼀个调试符号表.line:原始C源程序中的⾏号和.text节中机器指令之间的映射..strtab:⼀个字符串表,其中内容包括.symtab和.debug节中的符号表,以及节头部中的节名字.符号和符号表每个可重定位⽬标模块m都有⼀个符号表,它包含m所定义和引⽤的符号的信息.在链接器上下⽂中,有三种不同的符号:1>由m定义并能被其他模块引⽤的全局符号.全局链接器符号对应于⾮静态的C函数以及被定义为不带C的static属性的全局变量.2>由其他模块定义并被模块m引⽤的全局符号.这些符号成为外部符号,对应于定义在其他模块中的C函数和变量.3>只被模块m定义和引⽤的本地符号.有的本地符号链接器符号对应于带static属性的C函数和全局变量.这些符号在模块m中的任何地⽅都可见,但是不能被其他模块引⽤.⽬标⽂件中对应于模块m的节和相应的源⽂件的名字也能获得本地符号.符号表式有汇编器构造的,使⽤编译器输出到汇编语⾔.s⽂件中的符号.sysmab节中包含ELF符号表.这张符号表包含⼀个关于表⽬的数组.表⽬的格式如下:typedef struct{int name; //string table offsetint value; //section offset, or VM addressint size; //object size in byteschar type:4, //data, func, section, or src filebinding:4; //local or globalchar reserved; //unusedchar section; //section header index, ABS, UNDEF, or COMMON}Elf_Symbol;2.1.1符号解析链接器解析符号引⽤的⽅法是将每个引⽤和它输⼊的可重定位⽬标⽂件按的符号表中的⼀个确定的符号定义联系起来.对于那些和引⽤定义在相同模块的本地符号的引⽤,符号解析式⾮常简单明了的.编译器只允许每个模块中的每个本地符号只有⼀个定义.编译器还确保静态本地变量,它们会有本地链接器符号,拥有唯⼀的名字.对于全局符号的引⽤解析,当编译器遇到⼀个不是在当前模块中定义的符号(变量或函数名)时,它会假设该符号式在其他某个模块中定义的,⽣成⼀个链接器符号表表⽬,并把它交给链接器处理.如果链接器在它的任何输⼊模块中都找不到这个被引⽤的符号,它就输出⼀条错误信息并终⽌.在编译时,编译器输出的每个全局符号给汇编器,或者是强,或者是弱,⽽汇编器把这个信息隐含地编码在可重定位⽬标⽂件的符号表中.函数和以初始化的全局变量是强符号,未初始化的全局变量是弱符号.根据符号的强弱,有如下规则:1>不允许有多个强符号2>如果有⼀个强符号和多个弱符号,则选择强符号3>如果有多个弱符号,则任选⼀个弱符号与静态库链接所有编译系统都提供⼀种机制,将所有相关的⽬标模块打包为⼀个单独的⽂件,称为静态库,它可以⽤做链接器的输⼊.当链接器构造⼀个输出的可执⾏⽂件时,它只拷贝静态库⾥被应⽤程序引⽤的⽬标模块.在unix系统中,静态库以⼀种称为存档的特殊⽂件格式存放在磁盘中.存档⽂件是⼀组连接起来的可重定位⽬标⽂件的集合,有⼀个头部描述每个成员⽬标⽂件的⼤⼩和位置.链接器如何使⽤静态库来解析引⽤在符号解析阶段,链接器从左到右按照它们在编译驱动程序命令⾏上出现的相同顺序来扫描可重定位⽬标⽂件和存档⽂件.在这次扫描中,链接器位置⼀个可重定位⽬标⽂件集合E,这个集合中的⽂件会被合并起来形成可执⾏⽂件,和⼀个未解析的符号集合U,以及⼀个在前⾯输⼊⽂件中已定义的符号结合D.初始时,E,U,D都是空的.1>对于命令⾏上的每个输⼊⽂件f,链接器会判断f是⼀个⽬标⽂件还是⼀个存档⽂件.如果是⼀个⽬标⽂件,那么链接器把f添加到E,修改U和D 来反映f中的符号定义和引⽤,并继续下⼀个输⼊⽂件.2>如果f是⼀个存档⽂件,那么链接器就尝试匹配U中未解析的符号由存档⽂件成员定义的符号.如果某个存档⽂件成员m,定义了⼀个符号来解析U中的⼀个引⽤,那么就将m加到E中,并且链接器修改U和D来反映m中的符号定义和引⽤.对存档⽂件中的所有成员⽬标⽂件都反复进⾏这个过程,知道U和D都不再发⽣变化.在此时,任何不包含在E中的成员⽬标⽂件都会被丢弃,⽽链接器将继续到下⼀个输⼊⽂件.3>如果当链接器完成对输⼊命令⾏的扫描后,U是⾮空的,那么链接器就会输出⼀个错误并终⽌.否则,它会合并重定位E中的⽬标⽂件,从⽽构建输出的可执⾏⽂件.这种⽅式,导致了在输⼊命令时要考虑到,静态库和⽬标⽂件的位置,库⽂件放在⽬标⽂件的后⾯,如果库⽂件之间有引⽤关系,则被引⽤的库放在后⾯.2.1.2重定位当链接器完成了符号解析这⼀步时,它就把代码中的每个符号引⽤和确定的⼀个符号定义(也就是,它的⼀个输⼊⽬标模块中的⼀个符号表表⽬)联系起来.此时,链接器就知道它的输⼊⽬标模块中的代码节和数据解的确切⼤⼩.然后就开始重定位步骤.重定位由两步组成:1>重定位节和符号定义:在这⼀步中,链接器将所有相同类型的节合并为⼀个新的聚合节.然后,链接器将运⾏时存储器地址赋值给新的聚合节,赋给输⼊模块定义的每个节,以及赋给输⼊模块定义的每个符号.当这⼀步完成时,程序中的每个指令和全局变量都⼀个唯⼀的运⾏时存储器地址.2>重定位节中的符号引⽤:在这⼀步中,链接器修改代码节和数据节中对每个符号的引⽤,使得它们指向正确的运⾏时地址.为了执⾏这⼀步,链接器依赖于称为重定位表⽬的可重定位⽬标模块中的数据结构.重定位表⽬:当汇编器⽣成⼀个⽬标模块时,它并不知道数据和代码最终将存放在存储器中的什么位置.它也不知道这个模块引⽤的任何外部定义的函数或者全局变量的位置.所以,⽆论何时汇编器遇到对最终位置未知的⽬标引⽤,它就会⽣成⼀个重定位表⽬,告诉链接器在将⽬标⽂件合并为可执⾏⽂件时,如何修改这个引⽤.代码的重定位表⽬放在.rel.text中.已初始化数据的重定位表⽬放在rel.data中.ELF重定位表⽬的格式如下:typedef struct{int offset; //offset of the reference to relocateint symbol:24, //symbol the reference point totype:8; //relocation type} Elf32_Rel;ELF定义了11中不同的重定位类型,其中最基本的两种重定位类型是:R_386_PC32(重定位⼀个使⽤32PC相关的地址引⽤)和R_386_32(重定位⼀个使⽤32位绝对地址的引⽤).2.2.动态链接器共享库是⼀个⽬标模块,在运⾏时,可以加载到任意的存储器地址,并在存储器中和⼀个程序链接起来.这个过程称为动态链接,是由动态链接器完成的.共享库的共享在两个⽅⾯有所不同.⾸先,在任何给定的⽂件系统中,对于⼀个库只有⼀个.so⽂件.所有引⽤该库德可执⾏⽬标⽂件共享这个.so ⽂件中的代码和数据,⽽不是像静态库德内容那样被拷贝和嵌⼊到引⽤它们的可执⾏的⽂件中.其次,在存储器中,⼀个共享库的.text节只有⼀个副本可以被不同的正在运⾏的进程共享.。
c语言链接原理
c语言链接原理C语言链接原理什么是链接链接是将多个源文件组合成一个可执行的程序的过程。
在C语言中,链接分为静态链接和动态链接两种方式。
静态链接静态链接是在编译时将所有源文件和依赖的库文件打包合并为一个可执行文件。
在静态链接的过程中,会将所有被调用的函数和符号解析成绝对地址,并进行地址重定位,以便在程序运行时能正确找到对应的函数或变量。
静态链接的好处是,可执行文件独立,不依赖于外部的库文件,可以方便地在不同操作系统或机器上进行传输和运行。
但同时,也会使得可执行文件的大小变大,并且无法共享已被其他程序加载的库文件。
动态链接动态链接是在程序运行时,将程序所需要的库文件动态加载到内存中,并建立起调用关系。
相比于静态链接,动态链接的主要优势在于节省了磁盘空间,同时可以方便地共享已加载的库文件。
在动态链接的过程中,程序除了需要链接器的支持外,还需要动态链接器或运行时链接器(如)的支持。
动态链接器会根据程序中对函数和符号的引用,到指定的共享库文件中查找对应的函数或变量地址,并进行重定位。
这种方式需要在程序运行时动态解析符号地址,因此速度可能比静态链接慢一些。
符号解析在链接的过程中,一个非常重要的步骤就是符号解析。
符号解析是将函数名或变量名与其对应的地址进行关联的过程。
在C语言中,通过extern关键字来声明外部变量或函数。
在链接时,链接器会根据这些声明找到对应的定义,并确定其地址。
符号解析的过程是由链接器完成的,它会先查找目标文件中是否存在该符号的定义,如果存在则将其地址记录下来,否则会继续在其他目标文件或库文件中进行查找。
如果所有的目标文件和库文件都没有找到符号的定义,链接器将会报链接错误。
链接顺序在进行静态链接时,链接器需要按照一定的顺序来合并多个目标文件和库文件。
常见的链接顺序是从左到右,从上到下。
这个顺序决定了符号解析的优先级,后面的文件中的符号会覆盖前面的文件中的同名符号。
如果出现了重复定义符号的情况,链接器会报重复定义的错误。
