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关键字来声明外部变量或函数。
在链接时,链接器会根据这些声明找到对应的定义,并确定其地址。
符号解析的过程是由链接器完成的,它会先查找目标文件中是否存在该符号的定义,如果存在则将其地址记录下来,否则会继续在其他目标文件或库文件中进行查找。
如果所有的目标文件和库文件都没有找到符号的定义,链接器将会报链接错误。
链接顺序在进行静态链接时,链接器需要按照一定的顺序来合并多个目标文件和库文件。
常见的链接顺序是从左到右,从上到下。
这个顺序决定了符号解析的优先级,后面的文件中的符号会覆盖前面的文件中的同名符号。
如果出现了重复定义符号的情况,链接器会报重复定义的错误。
c语言源程序的编译和链接过程
c语言源程序的编译和链接过程C语言是一种广泛使用的编程语言,它的源程序需要经过编译和链接过程才能生成可执行文件。
在本文中,我们将详细介绍C语言源程序的编译和链接过程。
我们需要了解编译的概念。
编译是将高级语言(如C语言)源程序转换为机器语言的过程。
在C语言中,编译器负责将源代码转换为汇编代码,汇编代码是一种低级语言,更接近机器语言。
编译的过程包括词法分析、语法分析、语义分析等多个阶段。
在词法分析阶段,编译器将源代码分解为单词(token),并识别每个单词的类型。
在语法分析阶段,编译器根据语法规则对单词进行组织和分析,生成抽象语法树。
在语义分析阶段,编译器对抽象语法树进行语义检查,确保代码的合法性和正确性。
编译的过程可以用下图来表示:1. 源代码 -> 词法分析 -> 单词流2. 单词流 -> 语法分析 -> 抽象语法树3. 抽象语法树 -> 语义分析 -> 语义检查4. 语义检查通过 -> 汇编代码生成在编译过程中,编译器会生成汇编代码,汇编代码是一种与机器语言相似的低级语言。
汇编代码是由一系列的指令组成,每条指令都对应着机器语言中的一条指令。
汇编代码的生成是根据源代码的语义进行的,它与具体的硬件架构相关。
接下来,我们来介绍链接的概念。
链接是将多个目标文件(包括汇编代码和库文件)合并成一个可执行文件的过程。
在C语言中,链接器负责将编译生成的目标文件进行链接。
链接的过程包括符号解析、重定位等多个阶段。
在符号解析阶段,链接器会解析目标文件中的符号引用,找到对应的符号定义。
在重定位阶段,链接器会根据目标文件中的重定位信息,调整目标文件中的地址,使得它们能够正确地在内存中执行。
链接的过程可以用下图来表示:1. 目标文件 -> 符号解析 -> 符号表2. 目标文件 -> 重定位 -> 可执行文件在链接过程中,链接器会生成可执行文件,可执行文件是可以在特定平台上直接运行的文件。
c语言预处理编译汇编链接
c语言预处理编译汇编链接摘要:一、C 语言预处理1.预处理概述2.宏定义与替换3.文件包含4.条件编译二、编译与汇编1.编译概述2.汇编语言3.汇编器4.目标文件三、链接1.链接概述2.静态链接与动态链接3.链接器4.程序装载正文:C 语言是一种广泛应用于系统开发的高级编程语言。
它的程序设计过程包括预处理、编译、汇编和链接四个步骤。
一、C 语言预处理预处理是C 语言程序设计的第一步。
它主要处理以“#”开头的预处理指令。
预处理主要包括宏定义与替换、文件包含和条件编译三个方面。
1.宏定义与替换:在预处理阶段,程序员可以定义宏,用以简化复杂的表达式或代码。
预处理器会将程序中所有的宏名替换为宏定义的内容。
2.文件包含:预处理器还会处理文件包含指令,可以将其他源文件的内容嵌入到当前源文件中。
3.条件编译:根据不同的条件,选择性地编译不同的代码,以便于程序的调试和维护。
二、编译与汇编编译阶段是将C 语言源代码转换为目标平台的机器码。
编译器会分析源代码,生成一个中间代码表示,然后通过汇编器将其转换为机器码。
1.编译概述:编译阶段主要完成词法分析、语法分析、语义分析、中间代码生成和优化等任务。
2.汇编语言:汇编语言是一种低级编程语言,它用助记符表示机器码的操作。
汇编器将汇编语言翻译成机器码。
3.目标文件:编译器将源代码编译为机器码后,生成一个目标文件,它包含了程序的机器码和符号表等信息。
三、链接链接阶段是将编译生成的目标文件链接为一个可执行程序。
链接器会解析各个目标文件中的符号表,将它们链接在一起,形成一个完整的地址空间。
1.链接概述:链接阶段主要完成目标文件的装载、符号解析和重定位等任务。
2.静态链接与动态链接:根据链接方式的不同,链接可分为静态链接和动态链接。
静态链接将所有目标文件链接为一个大文件,而动态链接则将部分代码和数据留给操作系统在运行时动态加载。
3.链接器:链接器负责将各个目标文件链接为一个可执行程序。
C语言程序的编译流程
C语言程序的编译流程C语言是一种高级程序设计语言,常用于开发各种应用程序和系统软件。
在将C语言程序转化为可执行的计算机程序之前,需要经过编译的流程。
本文将详细介绍C语言程序的编译流程,包括预处理、编译、汇编和链接等步骤。
1. 预处理(Preprocessing)在编译过程中的第一步是预处理。
预处理器会对源代码进行处理,去除注释、替换宏定义、展开头文件等。
预处理的输出是一个经过修改的源文件,通常以.i作为文件扩展名。
预处理器还可以通过条件编译来控制程序中特定代码块的编译。
这对于根据不同平台或配置条件选择不同代码实现非常有用。
2. 编译(Compiling)预处理之后,进入编译阶段。
编译器会将预处理生成的.i文件翻译成汇编语言。
汇编语言是一种简单的低级语言,使用助记符来表示计算机指令。
编译的输出通常以.s作为文件扩展名。
编译器会对源代码进行语法分析和语义分析,并将其转化为中间表示。
中间表示是一种介于源代码和汇编语言之间的抽象语言形式,使得优化和目标代码生成更容易。
3. 汇编(Assembling)在汇编阶段,汇编器将汇编语言翻译成机器语言。
机器语言是计算机可以直接执行的二进制指令。
汇编的输出通常以.obj或.o作为文件扩展名。
汇编器会将汇编代码转化为可重定位目标代码(relocatable object code)。
可重定位目标代码包含机器指令、符号表和重定位信息等。
4. 链接(Linking)最后一步是链接阶段。
链接器将一个或多个目标文件链接在一起,形成最终的可执行文件。
链接的输出可以是可执行文件、静态库或动态库。
链接器会解析目标代码中的符号引用,并将其与其他目标文件中的符号定义进行关联。
同时,链接器还会执行地址重定位,将目标文件中的相对地址转化为绝对地址,以便正确地执行程序。
链接可以分为静态链接和动态链接。
静态链接将编译后的目标代码和库代码合并在一起,生成独立的可执行文件。
动态链接则在程序运行时才将所需的库代码加载到内存中。
C程序编译步骤详解
C程序编译步骤详解C程序的编译过程涉及多个步骤,包括预处理、编译、汇编和链接。
在这篇文章中,我们将详细介绍每个步骤的目的和执行过程。
1. 预处理(Preprocessing)预处理是编译过程的第一步。
预处理器通过处理以“#”开头的预处理指令,对源代码进行一系列宏展开和条件编译等操作,生成被后续编译器处理的中间代码。
预处理的目的是对源代码进行预处理,比如宏替换、头文件包含、条件编译等。
预处理器还会去除注释和空格,生成干净的中间代码。
预处理器的工作过程如下:a.替换宏:将源代码中定义的宏替换成它们对应的值。
b. 处理头文件:将#include指令所包含的头文件内容插入到源代码中,形成一个完整的源代码文件。
c.条件编译:根据条件编译指令,决定是否对段代码进行编译。
d.删除注释和空格:去除源代码中的注释和多余的空格。
e.生成中间代码:将预处理后的代码保存为一个临时文件,供后续步骤使用。
编译是将预处理生成的中间代码转换成汇编代码的过程。
编译器会对每个独立的源代码文件进行编译,生成相应的汇编代码文件。
编译的目的是将中间代码转化为汇编代码,以便后续可以生成可执行文件。
编译器会对代码进行语法、语义等方面的分析和优化。
a.词法分析:将源代码分解成一系列的词法单元,比如标识符、关键字、操作符等。
b. 语法分析:将词法单元根据语法规则进行分析,构建语法树(Abstract Syntax Tree, AST)。
c.语义分析:对语法树进行类型检查和语义约束的校验,确保程序的正确性。
d.代码生成:将语法树转换为目标机器的汇编代码,包括生成符号表、分配寄存器等操作。
3. 汇编(Assembling)汇编是将编译生成的汇编代码转化为机器代码的过程。
汇编器将汇编代码转换成可执行的二进制指令,以供计算机执行。
汇编器的工作过程如下:a.汇编指令解析:将汇编代码解析为机器指令,并生成机器码。
c.生成目标文件:将机器指令保存为目标文件,供链接器使用。
C语言编译和连接
一、编译编译(compilation,compile)1、利用编译程序从源语言编写的源程序产生目标程序的过程。
2、用编译程序产生目标程序的动作。
编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。
编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;中间代码生成;代码优化;目标代码生成。
主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
(1)词法分析词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。
执行词法分析的程序称为词法分析程序或扫描器。
源程序中的单词符号经扫描器分析,一般产生二元式:单词种别;单词自身的值。
单词种别通常用整数编码,如果一个种别只含一个单词符号,那么对这个单词符号,种别编码就完全代表它自身的值了。
若一个种别含有许多个单词符号,那么,对于它的每个单词符号,除了给出种别编码以外,还应给出自身的值。
词法分析器一般来说有两种方法构造:手工构造和自动生成。
手工构造可使用状态图进行工作,自动生成使用确定的有限自动机来实现。
(2)语法分析编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。
编译程序的语法规则可用上下文无关文法来刻画。
语法分析的方法分为两种:自上而下分析法和自下而上分析法。
自上而下就是从文法的开始符号出发,向下推导,推出句子。
而自下而上分析法采用的是移进归约法,基本思想是:用一个寄存符号的先进后出栈,把输入符号一个一个地移进栈里,当栈顶形成某个产生式的一个候选式时,即把栈顶的这一部分归约成该产生式的左邻符号。
C语言编译、链接和运行详解
C语⾔编译、链接和运⾏详解1. 什么是编译1. 有了 C 源⽂件,通过编译器将其编译成obj⽂件(⽬标⽂件)。
2. 如果程序没有错误,没有任何提⽰,但在Debug⽬录下会出现⼀个Hello.obj⽂件,该⽂件称为⽬标⽂件2. 什么是链接1. 有了⽬标⽂件(.obj ⽂件),通过链接程序将其和运⾏需要的 c 库⽂件链接成exe ⽂件(可执⾏⽂件)。
2. 如果程序没有错误,没有任何提⽰,但在Debug⽬录下会出现⼀个项⽬名.exe⽂件,该⽂件称为可执⾏⽂件。
3. 为什么需要链接库⽂件呢? 因为我们的C 程序中会使⽤C 程序库的内容,⽐如<stdio.h> <stdlib.h>中的函数printf() system()等等, 这些函数不是程序员⾃⼰写的,⽽是 C 程序库中提供的,因此需要链接4. 你会发现链接后,⽣成的.exe⽂件,⽐obj⽂件⼤了很多3. 什么是运⾏1. 有了可执⾏的 exe ⽂件, 也称为可执⾏程序 (⼆进制⽂件)2. 在控制台下可以直接运⾏ exe ⽂件4. C 程序开发注意事项1对修改后的hello.c源⽂件需要重新编译链接,⽣成新的exe ⽂件后,再执⾏,才能⽣效。
5. 如果想只⽣成⽬标 exe ⽂件,不想执⾏结果.exe ⽂件,不要执⾏结果可以这样做:打开 VC++ 2010 ,调试–> ⽣成解决⽅案这样就不会弹出执⾏.exe ⽂件⽽当打开.exe ⽂件就是⽣成后的结果6. 编译、链接和运⾏的流程hello.c的源⽂件只有1KB编译后⽣成的⽬标⽂件只有4KB27KB,说明在链接的过程中把⼀些库函数资源⼀起链接到 exe ⽂件中,所以 exe ⽂件变⼤了7. C 程序开发注意事项21. C 程序的主体结构说明#include ...void main() { // {} 包括内容,称为函数体语句1;语句2;}2. C 程序源⽂件以“c”为扩展名。
C语言的多文件编译与链接
目录1.引言2.什么是多文件编译与链接3.好处和用途4.创建多文件项目– 4.1 创建源文件– 4.2 编写代码5.多文件编译– 5.1 单个源文件编译– 5.2 多个源文件的编译6.链接– 6.1 静态链接– 6.2 动态链接7.总结8.参考资料引言在C语言中,我们通常会编写大型程序,这些程序往往由多个源文件组成。
而在编程过程中,我们需要将这些源文件编译成可执行文件。
本文将介绍C语言中的多文件编译与链接的概念、好处以及具体的使用方法。
什么是多文件编译与链接多文件编译与链接是一种将多个源文件编译成可执行文件的技术。
在C语言中,源文件以".c"为扩展名,每个源文件通常包含一个或多个函数的定义。
而将多个源文件链接在一起,能够构建出一个完整的可执行文件。
好处和用途使用多文件编译与链接的主要好处是提高了代码的可维护性和可重用性。
将程序分割成多个源文件,每个源文件负责实现不同的功能,降低了代码的复杂度。
此外,多文件编译与链接也能够提高编译速度,因为只有在源文件发生变化时,才需要重新编译该源文件。
多文件编译与链接在实际应用中也有广泛的用途。
例如,当我们编写大型软件项目时,通常会将不同功能模块的代码分别存放在不同的源文件中,这样能够方便团队合作、模块的独立开发以及快速修改和升级。
创建多文件项目在开始介绍多文件编译与链接的具体使用方法之前,我们需要创建一个多文件项目。
4.1 创建源文件首先,我们需要创建多个源文件。
我们可以选择使用文本编辑器创建多个以".c"为扩展名的源文件,也可以使用集成开发环境(IDE)来创建这些文件。
假设我们的项目包含两个源文件:main.c和functions.c。
4.2 编写代码在main.c文件中,我们可以编写程序的主函数。
主函数是程序的入口点,它会调用其他函数来完成程序的功能。
在functions.c文件中,我们可以编写其他函数的定义。
这些函数可以被主函数或其他函数调用。
c语言三步编译链接
c语言三步编译链接1.编写程序在开始编译和链接之前,我们需要首先编写C语言程序。
可以使用任何文本编辑器,如Notepad++,Sublime Text等来编写程序。
为了演示,我们将在Windows命令提示符下编写一个简单的Hello World 程序。
首先打开命令提示符,进入放置程序的文件夹中。
然后输入以下命令以新建一个C源文件:```notepad hello.c```输入以下代码:```cinclude<stdio.h>int main(){printf("Hello World!");return0;}```这是一个非常简单的程序,它只输出一行文本“HelloWorld!”。
请不要担心代码中的语法,我们将在下一部分中详细讨论它。
2.编译程序在我们能够运行程序之前,我们需要编译它。
编译器将源代码转换为计算机可以理解的机器代码。
C语言通常使用GNU编译器集合中的gcc编译器。
在命令提示符中,输入以下命令以编译程序:```gcc hello.c-o hello```让我们看看此命令中的各个部分。
我们使用gcc编译器,并告诉它我们要编译的文件是hello.c。
然后,我们使用-o选项(小写字母o)指定输出的可执行文件的名称。
在这种情况下,它被命名为hello。
如果一切顺利,我们将在命令提示符下看到一些警告信息,并在同一目录中找到一个新文件hello.exe。
如果出现任何错误,请仔细检查您的代码并寻找任何语法错误。
3.链接程序现在我们已经编译了程序,但是我们仍然需要将它链接到其他库文件中。
链接器将编译器产生的多个目标文件链接在一起,以创建一个完整的可执行文件。
在本例中,我们不依赖任何其他库文件,但是我们仍然需要使用链接器将代码链接在一起。
在命令提示符下,输入以下命令链接程序:```gcc hello.o-o hello```在本命令中,我们使用-o选项指定最终可执行文件的名称,而该可执行文件的名称与我们在上一个命令中指定的名称相同。
C语言编译和链接
C语⾔编译和链接编译链接是使⽤⾼级语⾔编程所必须的操作,⼀个源程序只有经过编译、链接操作以后才可以变成计算机可以理解并执⾏的⼆进制可执⾏⽂件。
编译是指根据⽤户写的源程序代码,经过词法和语法分析,将⾼级语⾔编写的代码转变为功能有效的汇编代码。
编译过程如下:1、预编译过程在c语⾔的预编译过程中,主要是对宏定义、条件编译语句、头⽂件包含语句以及特殊符号进⾏处理。
对于宏定义语句,⽐如#define NAME “user”,则在预编译阶段,会将程序中所有的NAME都替换为usr,当前有⼀点值得注意的是字符串中的NAME是不会被替换的。
⽽#define语句在经过预编译后的代码中则不会出现。
宏定义中还有⼀个#undef,对于它的处理是从取消的位置开始,后⾯的代码中都不会进⾏替换了,同样的经过预编译后的代码不会有#undef语句了。
对于条件编译语句指的是#ifdef、#else、#ifndef、#elif、#endif、(注意还有#if)。
条件编译指的是——如果#ifdef debug,如果定义了debug,那么后⾯部分的代码就会被编译,否则编译#else后的代码。
对于条件编译语句,在预编译阶段处理它,是根据它过滤掉那些不必要的代码。
头⽂件包含指令,之所以有头⽂件是为了是某些定义可以让更多的c源程序使⽤。
在预编译的过程中,预编译程序会将头⽂件中所有的定义都添加到它的输出⽂件中去,⽽不再有头⽂件包含指令。
2、编译的过程经过预编译以后,将产⽣新的源程序。
编译程序将通过词法分析和语法分析,确认所有的指令都符合语法规则,并将其翻译成等价的中间代码或汇编代码。
3、优化及汇编过程将汇编代码翻译成机器指令——即⽣成计算机系统可识别的⼆进制代码。
汇编的过程实际上就是将汇编语⾔代码翻译成为机器语⾔的过程。
这时候⽣成的代码实际上并不能直接运⾏,要经过链接以后才可以运⾏。
链接过程如下:所谓链接的过程就是指,经过编译后将会⽣成⼀个⽬标⽂件,这个⽬标⽂件可能会调⽤printf等函数,对于printf函数,它的⽬标代码在系统的函数库中(⼀般⽤户⽤到的很多函数库都存在于/usr/lib或者/lib中),链接所要做的就是将这些函数库中相应的代码组合到⽬标⽂件中去。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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++ 开发系统 ) 。