c++程序链接的过程原理详解
c语言链表的原理
c语言链表的原理链表数据结构是计算机科学中最基础的数据结构之一。
链表是一个由节点组成的线性数据结构,节点在内存中分别分配,并通过指针链接在一起,形成一个链式结构。
而C语言是一种强大的编程语言,以其简洁的语言结构和高性能的特性,著名于世界。
C语言链表的原理C语言链表原理是指使用C语言编程语言实现链表数据结构。
在C语言中,数据可以通过指针进行引用,链表数据结构正是利用了这一特性。
C语言链表数据结构是基于节点的数据类型,每个节点中会包含一个指向下一个节点的指针。
首先,需要定义一个节点结构体用于存储数据和指向下一个节点的指针。
例如:```cstruct node {int data;struct node *next;};```其中,data变量用于存储节点的数据,next指针用于链接下一个节点。
这个节点结构体定义的时候需要注意,结构体类型中包含了自身类型的指针变量。
接下来,我们需要定义一个头结点,头结点是链表数据结构的入口,也是链表的第一个节点。
需要注意的是,头结点并不存储数据,其实只是一个指针变量。
例如:```cstruct node *head = NULL;```这个head指针变量,初始值为NULL,是作为链表的入口,并且在初始化时不能包含任何数据。
当有新的数据需要加入链表时,需要动态分配一个节点结构体,将数据存储在节点的data变量中,然后将该节点的指针链接到链表中。
例如:```cstruct node *new_node = (struct node *) malloc(sizeof(struct node));new_node->data = 10;new_node->next = head;head = new_node;```这里,我们动态分配了一个新的节点结构体,并将值10存储在新的节点中,接着将新节点的指针链接到了链表的头部,然后更新头结点的指针为新节点的指针。
在创建完链表后,可以通过遍历链表来访问链表数据。
c语言编译链接过程
C语言编译链接过程主要包括预处理、编译、汇编和链接四个阶段。
1. 预处理:预处理阶段主要处理以"#"开头的预处理指令,如宏定义、头文件包含等。
预处理器会根据这些指令对源代码进行处理,生成一个没有宏定义、没有注释、没有条件编译指令的纯C语言代码文件。
2. 编译:编译阶段将预处理后的代码文件转换为汇编代码。
编译器会对代码进行词法分析、语法分析和语义分析,生成相应的中间代码。
3. 汇编:汇编阶段将汇编代码转换为机器码。
汇编器会将汇编代码转换为可执行的机器指令,并生成目标文件。
4. 链接:链接阶段将目标文件和库文件进行链接,生成最终的可执行文件。
链接器会将目标文件中的符号引用与库文件中的符号定义进行匹配,解析符号引用,生成最终的可执行文件。
在链接过程中,还会进行地址重定位、符号解析、符号重定位等操作,以确保最终生成的可执行文件能够正确运行。
需要注意的是,编译链接过程可以分为静态链接和动态链接两种方式。
静态链接是将所有的目标文件和库文件都链接到最终的可执行文件中,而动态链接是在运行时将依赖的库文件加载到内存中。
c 的工作原理
c 的工作原理
C是一种通用的高级编程语言,广泛应用于系统软件开发和嵌
入式系统领域。
它的工作原理是通过将C代码编译成机器码
来实现程序的运行。
C语言工作的基本流程是:首先,程序员使用C语言编写源代码文件,源代码文件以.c作为扩展名。
然后,使用C编译器
对源代码进行编译,将其转换成机器码文件(也称为目标文件),目标文件以.obj作为扩展名。
接下来,链接器将目标文
件与其他库文件一起链接,生成可执行文件。
在编译过程中,C编译器会对源代码进行词法分析、语法分析
和语义分析。
词法分析器将源代码文件分解为一系列的词法单元,如关键字、标识符、运算符和常量等。
语法分析器根据语法规则,将词法单元组织成语法树,检查代码是否符合语法规范。
语义分析器对语法树进行类型检查,确保变量的使用是合法的,并进行符号表的管理。
在链接过程中,链接器将目标文件与库文件进行合并。
库文件通常包含一些常用的函数和数据结构,供程序调用。
链接器将这些函数和数据结构的引用替换为实际的地址,以便程序能够正确地调用它们。
最终生成的可执行文件可以在操作系统上运行。
当执行程序时,操作系统会将程序加载到内存中,并按照机器码的指令依次执行,从而完成程序的运行。
总结来说,C的工作原理是将C代码编译成机器码,并通过链接生成可执行文件,最终在操作系统上运行。
这个过程包括词法分析、语法分析、语义分析、目标文件生成和链接等步骤。
c++ 动态链接原理
c++ 动态链接原理C++ 中的动态链接是一种在程序运行时加载和链接库(通常是共享库或DLL)的机制。
这样的链接是动态的,因为它发生在程序运行时,而不是在编译时。
动态链接有许多优点,其中包括代码重用、节省内存和更容易的库更新。
以下是C++ 中动态链接的一般原理:1. 编写和编译源代码:开发人员首先编写源代码并使用编译器将其编译成目标文件(通常是二进制文件),这些目标文件包含了程序的函数和数据。
2. 生成共享库:将目标文件中的一组函数和数据编译成共享库(例如,.dll 文件)。
这个共享库包含可执行代码的实现。
3. 生成可执行文件:开发人员编写主程序并使用编译器将其编译成可执行文件。
在编译过程中,编译器会生成一些链接信息,以指示程序在运行时需要动态链接的库。
4. 运行时动态链接:当用户运行可执行文件时,操作系统负责加载可执行文件到内存中。
在运行时,操作系统发现了可执行文件中对共享库的引用,并且会动态地将这些共享库加载到内存中。
5. 符号解析:一旦库被加载到内存中,操作系统需要解析可执行文件中对库中符号的引用。
符号是指在程序中使用的函数和变量的名称。
符号解析的目的是建立可执行文件中的符号与库中实际地址的映射关系。
6. 地址重定向:通过符号解析,操作系统将可执行文件中的符号引用重定向到库中实际的地址。
这确保了程序可以正确地调用库中的函数和访问库中的变量。
7. 程序执行:一旦所有符号引用都被解析和重定向,程序就开始执行。
在运行时,程序可以动态地调用库中的函数和使用库中的数据。
总体来说,动态链接使得程序在运行时能够灵活地加载和使用共享库,从而实现了更好的代码重用、模块化和可维护性。
这个机制对于大型软件项目和系统的开发非常有用。
c语言编译原理详解
使用的gcc 命令是: gcc
对应于链接命令是 ld
总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。了解这四个过程中所做的工作,对我们理解头文件、库等的工作过程是有帮助的,而且清楚的了解编译链接过程还对我们在编程时定位错误,以及编程时尽量调动编译器的检测错误会有很大的帮助的。
第二个阶段编译、优化阶段。经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。
编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个 目标文件;
第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。
(3)可执行文件
它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。
一、编译过程
编译过程又可以分成两个阶段:编译和汇编。
1、编译
编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:
第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。
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++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码,然后由汇编器(assembler)翻译成机器代码(再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中;(2)链接器(linker)将一个个的目标文件(或许还会有若干系统库)链接在一起生成一个完整的可执行文件。
C语言提供了一种将多个目标文件打包成一个文件的机制,这就是静态链接库(static library)。
开发者在链接时只需指定程序库的文件名,链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块,并把(且只把)它们从库中拷贝出来参与构建可执行文件。
链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合:(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非空,链接器报错并停止动作。
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语言源代码需要经过编译器进行编译。
编译器会首先对源代码进行词法分析,将代码分解为一个个的标记(token),如关键字、标识符、运算符等。
然后进行语法分析,将标记按照语法规则组合成语法树。
同时,编译器会进行语义分析,检查变量的定义和使用是否符合规范,进行类型检查等。
接下来,编译器将语法树转换为中间代码(通常是类似于汇编语言的形式),并进行优化以提高程序的性能。
优化包括消除冗余代码、减少计算次数等。
最后,编译器将中间代码转换为机器码(二进制文件),生成可执行文件。
可执行文件包含了机器语言的指令、数据和其他可执行文件需要的附加信息。
当用户运行可执行文件时,操作系统会将其加载到内存中,并执行其中的机器指令。
操作系统负责管理程序的执行环境,将指令转换为具体的操作,如内存读写、输入输出等。
程序的执行过程涉及到内存管理、进程调度、权限管理等操作系统的功能。
在程序执行过程中,程序可能会与外部环境进行交互,如从键盘输入数据、向屏幕输出结果。
这种交互通过与操作系统的接
口来实现。
操作系统提供了一些系统调用,可以让程序访问操作系统的功能,如读写文件、网络通信等。
总而言之,C语言程序的运行原理是通过编译器将源代码转换为机器码的可执行文件,然后由操作系统加载并执行,期间涉及到编译、优化、内存管理、进程调度等多个步骤。
c语言源程序的编译和链接过程
c语言源程序的编译和链接过程C语言是一种广泛使用的编程语言,它的源程序需要经过编译和链接过程才能生成可执行文件。
在本文中,我们将详细介绍C语言源程序的编译和链接过程。
我们需要了解编译的概念。
编译是将高级语言(如C语言)源程序转换为机器语言的过程。
在C语言中,编译器负责将源代码转换为汇编代码,汇编代码是一种低级语言,更接近机器语言。
编译的过程包括词法分析、语法分析、语义分析等多个阶段。
在词法分析阶段,编译器将源代码分解为单词(token),并识别每个单词的类型。
在语法分析阶段,编译器根据语法规则对单词进行组织和分析,生成抽象语法树。
在语义分析阶段,编译器对抽象语法树进行语义检查,确保代码的合法性和正确性。
编译的过程可以用下图来表示:1. 源代码 -> 词法分析 -> 单词流2. 单词流 -> 语法分析 -> 抽象语法树3. 抽象语法树 -> 语义分析 -> 语义检查4. 语义检查通过 -> 汇编代码生成在编译过程中,编译器会生成汇编代码,汇编代码是一种与机器语言相似的低级语言。
汇编代码是由一系列的指令组成,每条指令都对应着机器语言中的一条指令。
汇编代码的生成是根据源代码的语义进行的,它与具体的硬件架构相关。
接下来,我们来介绍链接的概念。
链接是将多个目标文件(包括汇编代码和库文件)合并成一个可执行文件的过程。
在C语言中,链接器负责将编译生成的目标文件进行链接。
链接的过程包括符号解析、重定位等多个阶段。
在符号解析阶段,链接器会解析目标文件中的符号引用,找到对应的符号定义。
在重定位阶段,链接器会根据目标文件中的重定位信息,调整目标文件中的地址,使得它们能够正确地在内存中执行。
链接的过程可以用下图来表示:1. 目标文件 -> 符号解析 -> 符号表2. 目标文件 -> 重定位 -> 可执行文件在链接过程中,链接器会生成可执行文件,可执行文件是可以在特定平台上直接运行的文件。
c 的工作原理
c 的工作原理
C语言的工作原理是通过编写源代码,经过编译器编译后生成机器码,然后由操作系统加载并执行这些机器码。
以下是C 语言的工作原理的详细过程:
1. 编写源代码:将程序员的需求和逻辑转化为C语言的源代码,源代码是由一系列C语句组成的文本文件。
2. 编译源代码:使用C语言的编译器对源代码进行编译。
编译器会进行词法分析、语法分析、语义分析等处理,将源代码转化为一种中间代码(通常是汇编代码)。
3. 汇编:将中间代码转化为机器码指令。
这一步骤将使用汇编器,将汇编代码翻译成二进制的机器码。
每一条汇编指令都对应着一条特定的机器码指令。
4. 链接:在程序中引用的外部函数和库函数可能分布在不同的目标文件中,链接器将不同的目标文件连接在一起,生成一个可执行文件。
这个过程还包括解析符号引用、分配内存地址等操作。
5. 加载和执行:将可执行文件加载到内存中,由操作系统负责管理。
操作系统将程序的入口地址作为起点,按照顺序执行机器码指令,实现程序的功能。
总结来说,C语言的工作原理可以概括为:源代码编写→ 编译生成中间代码→ 汇编生成机器码→ 链接生成可执行文件
→ 加载到内存执行。
这个过程中,编译器、汇编器、链接器和操作系统起着重要的作用,共同完成将C语言源代码转化为具体运行在计算机上的程序的过程。
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语言两个字符串的连接
c语言两个字符串的连接C语言是一种高效且广泛应用于开发各种应用程序的编程语言,它支持多种数据类型和数据操作,其中包括字符串的处理。
字符串是由一组字符组成的数据类型,是C语言中的基本数据类型之一。
在C 语言中,字符串的连接是一种常见的操作,它是将两个或多个字符串合并成一个新的字符串。
C语言提供了多种方法来实现字符串的连接,包括使用字符串连接函数和手动拼接字符串等方法。
下面将分别介绍这些方法的实现原理和应用场景。
1. 使用字符串连接函数C语言提供了 strcat() 函数来实现字符串的连接。
该函数的原型如下:char *strcat(char *dest, const char *src);其中,dest 是目标字符串,src 是要连接的字符串。
该函数将src 字符串追加到 dest 字符串的末尾,并返回连接后的字符串。
下面是一个实现字符串连接的示例代码:```#include <stdio.h>#include <string.h>int main(void){char str1[50] = "Hello, ";char str2[30] = "World!";strcat(str1, str2); // 将 str2 连接到 str1 后面printf("%s\n", str1); // 输出连接后的字符串return 0;}```在这个示例代码中,使用了 strcat() 函数将 str2 字符串追加到 str1 字符串的末尾。
最终输出的字符串为 "Hello, World!"。
该方法适用于需要连接两个字符串的场景,可以方便地实现字符串的拼接,但需要注意目标字符串的内存空间是否足够。
2. 手动拼接字符串除了使用字符串连接函数,还可以手动实现字符串的连接,即直接将两个字符串中的字符逐个拼接起来。
链接及编译详解
链接及编译详解这个问题没有具体指定要解释的是哪个链接及编译,所以我将给出一个简单的解释和示例来解释链接和编译的概念。
链接和编译是软件开发过程中的两个重要步骤。
编译是将程序源代码转换为计算机可以理解的机器代码的过程,链接是将各个模块的机器代码合并成一个可执行的程序。
编译过程通常包括以下几个步骤: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语言链接过程简介
1.链接器的意义链接器的主要作用是把各个模块之间相互作用的部分处理好,使得各个模块之间能够正确衔接。
2.模块链接(1)静态链接由链接器在链接时将库的内容直接加入到可执行程序中。
(2)Linux下静态库的创建和使用①编译静态库源码:gcc -c lib.c -o lib.o②生成静态库文件:ar -q lib.a lib.o③使用静态库编译:gcc main.c lib.a -o main.out静态链接示例示例:test.c文件#include <stdio.h>extern char* name();extern int add(int a, int b);int main(void){printf("Name: %s\n", name());printf("Result: %d\n", add(2,3));return 0;}slib文件char* name(void){return "Static Lib";}int add(int a, int b){return a + b;}编译静态库源码:gcc -c slib.c -o slib.o生成slib.o文件创建静态库文件:ar -q slib.a slib.o生成slib.a静态库文件使用静态库编译:gcc test.c slib.a -o test.out生成test.out可执行文件输出:Name: Static LibResult: 5(3)动态链接①可执行程序在运行时才动态加载库进行链接。
②库的内容不会进入可执行程序当中。
(4)Linux下动态库的创建和使用①编译动态库源码:gcc -shared dlib.c -o dlib.so②使用动态库编译:gcc main.c -ldl -o main.out③关键系统调用dlopen: 打开动态库文件。
dlsym: 查找动态库中的函数并返回调用地址。
c语言链接程序
c语言链接程序c语言链接程序由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。
所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:(1)静态链接在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。
这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。
静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
(2)动态链接在此种方式下,函数的`代码被放到称作是动态链接库或共享对象的某个目标文件中。
链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。
在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。
动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。
使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。
但并不是使用动态链接就一定比使用静态链接要优越。
在某些情况下动态链接可能带来一些性能上损害。
下载全文。
C语言中的链接编写
C语言中的链接编写如果你有一个c++做的动态链接库.so文件,而你只有一些相关类的声明, 那么你如何用C调用呢?C语言中的链接编写再怎么写呢?欢迎大家阅读!更多相关信息请关注相关栏目!链接链接就是将不同部分的代码和数据收集和组合成为一个单一文件的过程,这个文件可被加载或拷贝到存储器执行.链接可以执行与编译时(源代码被翻译成机器代码时),也可以执行与加载时(在程序被加载器加载到存储器并执行时),甚至执行与运行时,由应用程序来执行.在现代系统中,链接是由链接器自动执行的.链接器分为:静态链接器和动态链接器两种.静态链接器静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出.静态链接器主要完成两个任务: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 offset int value; //section offset, or VM address int size; //object size in bytes char type:4, //data, func, section, or src file binding:4; //local or global char reserved; //unused char section; //section header index, ABS, UNDEF, or COMMON}Elf_Symbol;符号解析链接器解析符号引用的方法是将每个引用和它输入的可重定位目标文件按的符号表中的一个确定的符号定义联系起来.对于那些和引用定义在相同模块的本地符号的引用,符号解析式非常简单明了的.编译器只允许每个模块中的每个本地符号只有一个定义.编译器还确保静态本地变量,它们会有本地链接器符号,拥有唯一的名字.对于全局符号的引用解析,当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,它会假设该符号式在其他某个模块中定义的,生成一个链接器符号表表目,并把它交给链接器处理.如果链接器在它的任何输入模块中都找不到这个被引用的符号,它就输出一条错误信息并终止.在编译时,编译器输出的每个全局符号给汇编器,或者是强,或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表中.函数和以初始化的全局变量是强符号,未初始化的全局变量是弱符号.根据符号的强弱,有如下规则: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中的目标文件,从而构建输出的可执行文件.这种方式,导致了在输入命令时要考虑到,静态库和目标文件的位置,库文件放在目标文件的后面,如果库文件之间有引用关系,则被引用的库放在后面.重定位当链接器完成了符号解析这一步时,它就把代码中的每个符号引用和确定的一个符号定义(也就是,它的一个输入目标模块中的一个符号表表目)联系起来.此时,链接器就知道它的输入目标模块中的代码节和数据解的确切大小.然后就开始重定位步骤.重定位由两步组成: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位绝对地址的引用).动态链接器共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并在存储器中和一个程序链接起来.这个过程称为动态链接,是由动态链接器完成的.共享库的共享在两个方面有所不同.首先,在任何给定的文件系统中,对于一个库只有一个.so文件.所有引用该库德可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库德内容那样被拷贝和嵌入到引用它们的可执行的文件中.其次,在存储器中,一个共享库的.text节只有一个副本可以被不同的正在运行的进程共享.多目标文件的链接stack.c#include#define STACKSIZE 1000typedef struct stack {int data[STACKSIZE];int top;} stack;stack s;int count = 0;void pushStack(int d){s.data[s.top ++] = d;count ++; } int popStack(){return s.data[-- s.top];}int isEmpty(){return s.top == 0;}link.c#includeint a, b;int main(){a =b = 1;pushStack(a);pushStack(b);pushStack(a);while (!isEmpty()) {printf(%dn, popStack());}return 0;}编译方式:gcc -Wall stack.c link.c -o main提示出错信息如下:但是代码是可以执行的定义和声明static和extern修饰函数上述编译出现错误的原因是:编译器在处理函数调用代码时没有找到函数原型,只好根据函数调用代码做隐式声明,把这三个函数声明为:int pushStack(int); int popStack(void); int isEmpty(void);编译器往往不知道去哪里找函数定义,像上面的例子,我让编译器编译main.c,而这几个函数定义却在stack.c里,编译器无法知道,因此可以用extern声明。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
许多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(){return0;}其中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(){return0;}用下面这个命令编译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++开发系统)。
VC的链接器是link.exe,因为main.obj保存了缺省库信息,所以可以用link main.obj libc.lib或者link main.obj来生成可执行文件main.exe,这两个命令是等价的。
但是如果你用link main.obj libcd.lib的话,链接器会给出一个警告:"warning LNK4098:defaultlib"LIBC"conflicts with use of other libs;use/NODEFAULTLIB:library",因为你显式指定的标准库版本与目标文件的缺省值不一致。
通常来说,应该保证链接器合并的所有目标文件指定的缺省标准库版本一致,否则编译器一定会给出上面的警告,而LNK2005和LNK1169链接错误则有时会出现有时不会。
那么这个有时到底是什么时候?呵呵,别着急,下面的一切正是为喜欢追根究底的你准备的。
建一个源文件,就叫mylib.c,内容如下:/*mylib.c*/#i ncludevoid foo(){printf("%s","I am from mylib!/n");}用cl/c/MLd mylib.c命令编译,注意/MLd选项是指定libcd.lib为默认标准库。
lib.exe是VC自带的用于将目标文件打包成程序库的命令,所以我们可以用lib/OUT:my.lib mylib.obj将mylib.obj打包成库,输出的库文件名是my.lib。
接下来把main.c改成:/*main.c*/void foo();int main(){foo();return0;}用cl/c main.c编译,然后用link main.obj my.lib进行链接。
这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169链接错误,你仅仅是得到了一条警告信息:"warning LNK4098:defaultlib"LIBCD"conflicts with use of other libs;use/NODEFAULTLIB:library"。
我们根据前文所述的扫描规则来分析一下链接器此时做了些啥。
一开始E、U、D都是空集,链接器首先扫描到main.obj,把它加入E集合,同时把未解析的foo加入U,把main加入D,而且因为main.obj 的默认标准库是libc.lib,所以它被加入到当前输入文件列表的末尾。
接着扫描my.lib,因为这是个库,所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配,看是否有模块定义了U中的符号。
结果mylib.obj确实定义了foo,于是它被加入到E,foo从U转移到D,mylib.obj引用的printf加入到U,同样地,mylib.obj指定的默认标准库是libcd.lib,它也被加到当前输入文件列表的末尾(在libc.lib的后面)。
不断地在my.lib库的各模块上进行迭代以匹配U中的符号,直到U、D都不再变化。
很明显,现在就已经到达了这么一个不动点,所以接着扫描下一个输入文件,就是libc.lib。