《C专家编程》:对链接和函数库的思考(五)

        当我们开始编程时,就惊奇的发现要让程序正确运转比想象的要难。我们不得不使用给调试结束。我还清楚的记得那一刻,从那时开始我就领悟到,从我自己的程序里寻找错误将成为我生活的一个重要部分。----Maurice Wikes发现Bug,1949.
  一、编译器

       编译器创建一个输出文件,这个文件包含了可重定向的对象。这些对象就是与源程序对应的数据和机器指令。绝大多数的编译器并不是一个单一庞大的程序。他们通常由多达六七个稍小的程序所组成。这些可以方便从编译器中分离出来的单独程序包括:预处理器(preprocessor)、语法和语意检查器(synstatic and semantic checker)、代码生成器(code generate)、汇编程序(assembler)、优化器(optimizer)、连接器(linker)。当然还包括一个调用所有这些程序并向各个程序传递正确选项的驱动程序(driver program)。优化器几乎可以加在上述所有阶段的后面。当前的编译器的优化器措施主要在前端和后端之后的表示层之间执行。他们之所以分割为几个部分,主要是因为程序中如果每个具有特定功能的部分自身都是一个完整的程序,高内聚低耦合就会更容易设计和维护。例如控制预处理器过程的规则是预处理器所独有的,跟C语言其他部分没有多少共同之处。分割的小程序如下图:


         源文件(.c)生成目标文件(.o)并不能直接执行,它首先需要载入到连接器中。连接器确认main函数为初始进入点(程序开始执行的地方),把符号引用(symbolic reference)绑定到内存地址,把所有的目标文件集中在一起,从而产生可执行文件(.exe).

二、函数库

     如果函数库的一份拷贝是可执行文件的物理组成部分,那么我们称之为静态链接,静态链接库被称为:archive,以(.a)为扩展名

     如果可执行文件只包含了文件名,让载入器在运行时刻寻找到程序所需要的函数库, 那么我们称之为动态链接,表示(shared object),以(.so)为扩展名
     1、动态链接的目的之一是:ABI(Application Binary Interface).应用程序二进制接口。
     动态链接的目的主要是把程序与他们使用的特定的函数库版本中分离开来,取而代之的是,我们约定由系统向程序提供一个接口,该接口保持稳定,不随时间和操作系统的
后续变化发生变化。程序可以调用接口所承诺的服务,而不必关心该功能使如何提供和实现的。由于它是介于应用程序和函数库二进制可执行文件所提供的服务之间的接口,
所以称之为:应用程序二进制接口(ABI)。
     动态链接是一种“Just in tiime”(JIT),这意味着程序必须在运行时能够找到他们所需要的函数库。连接器通过把库文件名或者路径名植入程序中来做到这一点。这意味着函数库的路径名不能随意改动。
     2、动态链接的优点:
      (1)、 体积小。动态链接是一种更为现代的方法。它的优点是可执行文件的体积可以非常小。虽然运行速度稍慢些,但动态链接能够更加有效的利用磁盘空间,因为函数库只有在需要时才被映射到进程中。而且链接-编辑阶段的时间也会缩短(因为链接器的有些工作呗推迟到载入时)。 以前,避免把函数库的拷贝绑定到每个执行文件的唯一方法就是把服务置于内核中而不是函数库中,这就带来了可怕的“内核膨胀”问题。
      (2)、 节省了物理空间。所有的动态链接到某个特定的函数库的可执行文件在运行时共享该数据库的一个单独拷贝。擦偶偶系统内核保证映射到内存中的函数库可以被所有使用它们的进程共享。这就提供了更好的I/O和交换空间利用率,节省了物理空间,从而提高了整体的性能。如果是可执行文件是静态的,每个文件都有一份函数库的拷贝,显然极为浪费。
       例如你有八个基于XView函数库的应用程序正在运行,如果函数库是动态链接的,只需要把一个XView函数库文本段映射到内存中。第一个进程的mmap(就是一种把文件映射到内存的机制)调用将使内核把共享对象映射到内存中,其余7个进程的mmap调用将使内核把已经映射到内存中的对象由各个进程共享。这八个进程每一个都将共享内存中同一份XView的函数库拷贝。如果函数库是静态链接的,将会有八份函数库拷贝映射到内存中,这将消耗更多的物理内存,引起更多的换页。
       (3)、 动态链接使的函数库的版本升级更容易。新的函数库可以随时发布,只要安装到系统中,旧的程序就能够自动获得新版本函数库的优点而无需重新连接。能够提高系统的总体性能。
      使用静态链接的最大危险在于将来版本的操作系统可能与可执行文件所绑定的系统函数库不兼容。如果应用程序静态库链接于版本N的操作系统中,当把程序运行与版本为N+1的操作系统时,可能会立即崩溃或者出现一个不明显的错误。但是如果是动态链接,则它会正确的选择N+1版本的系统函数库,保证正确的运行。
       所以一个好的习惯就是:所有的应用程序都是用动态链接。但是也要考虑移植和兼容性的问题。
  创建动态链接库的最简单形式:
#vim file.c
my_lib_function()
{
   printf("library routing called!");
}
利用cc命令加上-G选项来创建:
#cc -o libfunction.so -G file.c
然后就可以利用这个动态库来编写程序了,并且使用下面这种方法与函数库进行链接:
#vim test.c
include <stdio.h>
int main()
{
  my_lib_function();
}
#cc test.c -L/home/linden -R/home/linden -lfunction
#a.out
结果:library routing called!
-L/home/linden:告诉链接器在链接时从哪个目录开始寻找需要的函数库;
-R/home/linden:告诉程序运行时在那个目录里找需要链接的函数库;
三、函数库链接的5个特点 
(1)动态文件库的扩展名为(.so),而静态文件的扩展名为(.a);
(2)例如,你通过-lthread选项,告诉编译链接到libthread.so。编译器被告知根据选项-lname链接到相应的函数库,函数库的名字为libname.so,换句话说,“lib”和文件的扩展名被省掉了,但在前面加了个“l”。
(3)编译器期望在确定的目录中找到库编译器有自己的一套规则来寻找库函数,例如一些特殊的位置:/usr/lib中查找函数库;也可以用“-l”选项来让编译器在某个特定的目录下查找函数库;“-L” 和“-R”表示编译和运行时的函数库的位置。
(4)观察头文件,确认所使用的函数库
    我们怎么知道必须连接到那些函数库?
     一个很好的建议就是查看程序所使用的“#include”指令。在程序中所包含的每个头文件都可能代表一个必须连接的函数库。例如观察的源码,你会发现自己调用了一些自己不曾实现的函数。例如max(),sin(),cos()等。他们可以在math函数库中找到原型。
(5)与提取动态库的符号相比,静态库中的符号提取的方法限制更严。
    在动态链接中,所有的库符号都进入输出文件的虚拟地址空间中,所有的符号对于链接在一起所有文件都是可见的。但是静态不然,且较为复杂。
四、Interposition--也称interposing

       它的意思是通过编写与函数库同名的函数来取代该函数库的行为。以求的更快更高的效率。但是需要格外的小心。使用interposition后,系统版本的函数将被自己版本的同名函数所替代,不管是在自己的代码中还是在系统调用中。

五、轻松一下:Turing测验。
       因为以前在上课的时候听老师提过图灵奖获得者那可是计算机界的泰山北斗,十分厉害。也听过Turing Testing。非常有意思。由于计算机的飞速发展,计算机的能力已经超乎了我们的想象,记得在《失控》一书中有这样一句话:“机器,正在生物化;生物正在工程化。”我觉得还是非常有道理的。人工智能的飞速发展,已经使机器具有了人工智能,Goole的无人驾驶汽车和AlphaGo大胜韩国九段围棋手李世石,连人类最后的智慧堡垒也被攻破。日本的机器人和学生一起上课参加高考等等一些鲜活的实例告诉我们人工智能和机器学习已经有了里程碑式的突破。

    那么怎么辨别这台机器到底是否具备人工智能呢?
    对于机器人,当时做了这样一个Turing Testing:
   Turing提议有一位询问者与另一个人和一台计算机对话(通过电传形式,以避免视觉和声觉的线索)。如果在5分钟内,询问者无法分辨出哪个是人哪个是计算机,那么我们就认为该计算机具有了人工智能。

     对于测试的过程中,也出现了很多有意思的对话,有兴趣的可以上网查查资料,这里就不详细赘述了!
    Alan Turing被公认为计算机领域最伟大的理论先行者之一。为了纪念他,美国计算机协会把他的最高年度奖项命名为Turing Award(图灵奖)。1983年,图灵奖授予了Dennis Ritchis和Ken Thompson,以表彰他们在UNIX和C语言上的杰出贡献。

     但是遗憾的是Turing以一种很有个性的不切实际的方式自杀:吃了一个注射了氰化物的苹果。

     高山仰止,景行行止,虽不能至,然心向往之。借此机会表达一下对前辈的缅怀与敬意。



你可能感兴趣的:(链接,编译,动态库,静态库,函数库)