在C语言中。一个重要的思想就是分别编译。即若干个源程序能够在不同的时候单独进行编译。然后在恰当的时候整合到一起。可是连接器通常是与C编译器分离的,连接器怎样做到把若干个C源程序合并成一个总体呢?
典型的连接器把由编译器或汇编器生成的若干个目标模块。整合成一个被称为加载模块或可运行文件的实体,该实体可以被操作系统直接运行。当中。某些目标模块是直接作为输入提供给连接器的;而另外一些目标模块则是依据连接过程的须要。从包含有类似printf函数的库文件里取得的。
连接器通常把目标模块看成是由一组外部对象组成的。
每一个外部对象代表着机器内存中的某个部分。并通过一个外部名称来识别。
因此,程序中的每一个函数和每一个外部变量,假设没有被声明为static,就都是一个外部对象。
某些C编译器会对静态函数和静态变量的名称做一定改变,将它们也作为外部对象。
因为经过了“名称修饰”。所以它们不会与其它源程序文件里的同名函数或同名变量发生命名冲突。
大多数连接器都禁止同一个加载模块中的两个不同外部对象拥有同样的名称。然而。在多个目标模块整合成一个加载模块时,这些目标模块可能就包括了同名的外部对象。
连接器的一个重要工作就是处理这类命名冲突。
处理命名冲突的最简单办法就是干脆全然禁止。
对于外部对象是函数的情形,这样的做法是正确的。
一个程序假设包含两个同名的不同函数。编译器根本就不应该接受。而对于外部对象是变量的情形,问题就变得困难了。不同的连接器对这样的情形有着不同的处理方式。
如今讲讲连接器是怎样工作的?
连接器的输入是一组目标模块和库文件。连接器的输出是一个加载模块。
连接器读入目标模块和库文件。同一时候生成加载模块。
对每一个目标模块中的每一个外部对象,连接器都要检查加载模块,看是否已有同名的外部对象。假设没有,连接器就将该外部对象加入到加载模块中;假设有,连接器就要開始处理命名冲突。
除了外部对象之外,目标模块还可能包含了对其它模块中的外部对象的引用。比如:一个调用了函数printf的C程序所生成的目标模块。就包含了一个对函数printf的引用。
能够猜測得出。该引用指向的是一个位于某个库文件里的外部对象。在连接器生成加载模块的过程中。它必须同一时候记录这些外部对象的引用。当连接器读入一个目标模块时,它必须解析出这个目标模块中定义的全部外部对象的引用。并作出标记说明这些外部对象不再是没有定义的。