当前位置:C++技术网 > 精选软件 > C++语言零基础入门教程:3.3 链接代码是什么,为什么需要链接,如何链接代码

C++语言零基础入门教程:3.3 链接代码是什么,为什么需要链接,如何链接代码

更新时间:2015-10-24 17:51:04浏览次数:1+次

    在上一节课《C++语言零基础入门教程:3.2 C++手动编译代码,手动使用编译器编译代码》详细讲解了手动编译源代码的过程。因为编译这个理解起来是很简单的,但是为了深入了解一下,就介绍了手动编译的过程。
    那么本节课,我们接着介绍链接这个环节。手动链接的过程和手动编译差不多。因为编译器和链接器都在同一个目录下,只是文件名不一样。链接器的程序文件名为link.exe。所以在命令行中输入link即可是用链接器来链接了。
    那么简单介绍一下链接器。
    link.exe的输入文件包括obj文件、lib文件、exp文件、def文件、res文件、txt文件、ilk文件等等,而输出的文件一般是exe文件、dll文件、sys文件等可执行程序文件。链接器就是将输入的文件打包到输出文件中的。也就是说,输出的文件里,包含了输入的所有文件的内容。

    我们看看整个流程图:
    编译链接完整流程,链接代码是什么,为什么需要链接,如何链接代码
    从最顶部的.c和.cpp文本形式的源代码文件(也就是我们写的代码文件)开始,通过编译器编译成二进制的代码文件。这些二进制代码文件就是连接器输入的文件了。而我们关注最多的链接文件就是lib文件了。那么lib静态库和lib导入库的详细知识,请参考《lib库知识全面讲解1:静态链接lib库和lib导入库以及动态链接库dll的关系》以及“lib库知识全面讲解”系列文章。
    那么我们使用windows的命令行来手动链接,使用link命令。只要在后面填写所有需要链接的文件即可。参数和命令以及参数之间,用空格隔开就行了。如下所示:

link 1.obj 2.obj 3.res

     这样就可以了。而想要正确输入这个命令正常执行,请参考上一节的详细介绍过程。只需要将cl输入命令改成现在的这个命令即可,这里就不重复说了,自己动手吧。
    那么我们这里重点介绍链接代码是什么,为什么需要链接,我们的代码又怎么和链接的代码整合在一起呢?
    我们的exe程序文件,或者dll文件,都是PE格式文件。就是在windows上运行的可执行文件格式了。PE格式文件内部,装有程序执行需要的二进制代码和数据。PE文件的生成过程就是我们写代码生成exe文件的过程。这种文件格式很特别,所以,并不是随便用某个软件比如word打开后写几个字保存就可以的。
    exe文件等并不是数据文件,其他的office文件,文本文件还有其他各种各样的格式的文件,都是数据文件。数据文件只是供软件读取的数据。而exe等可执行文件里含有代码,可以在cpu上执行,用来操作计算机的。而可执行文件里也存储了一些数据,主要是给自己程序使用的,当然很多dll文件,专门存储数据,可以给其他exe程序使用。
    数据文件中存储的数据只能被看做是数据,不能当做代码执行。而可执行文件中的数据,根据内部格式标志的,分为数据和代码,代码部分就可以被拿来执行,数据部分也不能拿来执行的。
    而代码部分,其实也不是一个整体的,而是要分很多模块的。不同的模块存放不同的代码。当然数据部分也会分模块,我们不深究这个怎么分之类的。代码的分块我们大概可以理解为不同的功能块。比如说我们写的代码,被编译成.obj的二进制格式的文件,最终将会被链接到一个指定的exe文件的某个位置,那么这一块就是我们的代码所在的区域。然后我们代码中要用到运行时库的函数,比如printf,这样我们就需要将运行时库链接进来,放在一个位置,分为一块。当然,我们程序能运行起来也都要运行时库的支持,那么这些都要链接起来。还有,假如我们程序中用到了第三方提供的库,那么我们还要将第三方的库链接进来。每一种自然都要占用一部分的区域。
    那么链接,实际上就是将需要的被编译好的二进制代码复制到我们的exe文件中。虽然这些被编译的文件人看不懂(也没法直接看,除非反编译),但是计算机看得懂就行。
    那么为什么需要链接呢?为什么我们不一次性都将代码写好编译就可以了呢?当然,这些库都是人家写好的,人家可以以两种形式给你。一种就是文本形式的源码文件,这样给你后,你就可以直接放入项目直接编译就好。这样就不需要额外链接了。但是人家愿意给你吗?这些代码是他们的心血呀,人家不一定愿意咯。当然,有些人也愿意,所以,这就是开源的含义了。开源就是将文本形式的代码开放出来,我们可以直接将文本形式的源代码直接放在项目编译就可以了。
    商业的代码,是拿来赚钱的,一般都是不能让你知道源码的,不然你也可以生成这个程序了,那他们还赚什么钱呢?他们投入的成本不就打水漂了。所以为了保护他们的代码,但是又能够给别人使用,所以就编译成二进制的代码。你没法看源码,所以无法修改。但是最终可以通过这种形式将二进制代码放在exe文件中,这样程序看得懂这些代码,自然也能运行。
    链接代码还有其他方面的考虑,比如一些基础库,是非常核心的代码,也不能被轻易修改,否则会出问题。还有,一个库非常常用,总不能到处分发源码文件吧,一是容易被修改,修改后就可能出错,而是不方面,每次使用的时候都要去加入这些文件。而做出库文件之后,设定好参数,编译器可以自己自动去将这些库链接到最终的exe文件中。这也提高了开发效率哦。
    最原始的应该就是只有编译这个过程,直接编译。编译所有的源代码,就成了exe。但是现在不可能做到了。除非你自己写一个编译器,写运行时库等等。也没有实际意义,技术发展总是与社会发展匹配的。
    我们的代码和库的二进制代码链接后都放在exe文件中,每一部分都占一个区域。那么我们代码中有调用函数的,就需要准确找到被调用的函数的位置。我们自己的代码中的函数调用,在编译时,都通过地址的形式确定好了。调用函数实际上就是代码跳转到被调用函数的代码区域执行。
    我们前面说的是将库中所有函数等的二进制实现代码复制到了exe文件里。但是这个每次都链接进来会导致文件很大。这个就是静态链接,链接的对象就是静态lib库。那么为了减小可执行文件大小,所以就不直接链接库的二进制代码,而是将dll对应的lib导入库的数据复制到exe中。这些数据只是说明实际的代码所在的位置,并没有函数实现代码。导入库的数据复制进去后,只是作为一个重定位的作用,相当于将函数调用引向真正的函数定义处,也就是dll里的函数定义。lib导入库的数据放在exe里就相当于占位,它架起了exe和dll之间的桥梁。这种链接其实并没有真正的链接到dll,而只是做了一个重定位表。在运行时,在将dll加载到内存中,然后才通过这个重定位表指定的位置调用dll区域的函数。导入库的重定位表的信息就是指定的函数在dll中的相对位置,加载dll后,就知道基地址,然后通过重定位表知道的相对位置,就可以移动到dll上的函数,这样就可以调用dll里的函数了。对于dll的函数的调用,我们可以不用导入库,而是加载后直接移动到dll中的函数所在的位置,这个就需要你主动的找了。