第四章 连接
因为编译器一般每次只处理一个文件,所以它不能检测出那些需要一次了解多个源文件才能察觉的错误。而且,在许多系统中连接器是独立于C语言实现的,因此如果前述错误的原因是与C语言相关的,连接器对此同样束手无策。
某些C语言实现提供了一个称为lint的程序,可以捕获大量的此类错误,但遗憾的是并非全部的C语言实现都提供该程序。
4.1 什么是连接器
C语言中的一个重要思想就是分别编译,即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合到一起。
典型的连接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体,该实体能够被操作系统直接执行。
连接器的输入是一组目标模块和库文件。连接器的输出是一个载入模块。对每个目标模块中的每个外部对象,连机器都要检查载入模块,看是否已有同名的外部对象。如果没有,连机器就将该外部对象添加到载入模块中,如果有,连机器就要开始处理命名冲突。
除了外部对象之外,目标模块中还可能包括了对其他模块中的外部对象的引用。在连机器生成载入模块的过程中,它必须同时记录这些外部对象的引用。当连机器读入一个目标模块时,它必须解析出这个目标模块中定义的所有外部对象的引用。并作出标记说明这些外部对象不再是未定义的。
4.2 声明与定义
int a;
如果其出现在所有的函数体之外,那么它就被称为外部对象a的定义。这个语句说明了a是一个外部整型变量,同时为a分配了存储空间。因为外部对象a并没有被明确指定任何初始值,所以它的初始值默认为0。
extern int a;
上述声明是一个对外部变量a的引用,a的存储空间在程序的其他地方分配,即使它出现在一个函数的内部。下面的函数srand在外部变量random_seed中保存了其整型参数n的一份拷贝:
void srand (int n)
{
extern int random_seed;
random_seed = n;
}
如果一个程序中包括了语句extern int a; 那么,这个程序就必须在别的某个地方包括语句int a; 这两个语句既可以在同一个源文件中,也可以位于程序的不同源文件中。
如果外部变量的多个定义各指定一个初始值,大多数系统都会拒绝接受该程序。但是,如果一个外部变量在多个源文件中定义却并没有指定初始值,那么某些系统会接受这个程序,而另外一些系统则不会接受。
4.3 命名冲突与static修饰符
两个具有相同名称的外部对象实际上代表的是同一个对象,即使编译者的本意并非如此,但系统却会如此处理。因此,如果在两个不同的源文件中都包括了定义
int a;
那么,它或者表示程序错误(如果连机器禁止外部变量重复定义的话),或者在两个源文件中共享a的同一个实例(无论两个源文件中的外部变量a是否应该共享)。
如果若干个函数需要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一个源文件中以static修饰符声明。
如果函数f需要调用另一个函数g,而且只有函数f需要调用函数g,可以把函数f与函数g都放到同一个源文件中,并声明函数g为static。
可以在多个源文件中定义同名的函数g,只要所有的函数g都被定义为static,或者仅仅只有其中一个函数g不是static。因此为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中其他函数调用,就应该声明该函数为static。
4.4 形参、实参与返回值
如果一个函数在被定义或者声明之前被调用,那么它的返回类型就默认为整型。
如果需要在两个不同的文件中分别定义函数main与函数square,那么该如何处理?函数square只能有一个定义。如果square的调用与定义分别位于不同的文件中,那么必须在调用它的文件中声明square函数。
如果一个函数没有float、short或者char类型的参数,在函数声明中完全可以省略参数类型的说明(注意,函数定义中不能省略函数类型的说明)。因此,即使是在ANSI C中,像下面这样的声明也是可以的:
double square();
这样做依赖于调用者能够提供数组正确且类型恰当的实参。这里的“恰当”并不意味着“等同”:float类型的参数会自动转换为double类型,short或者char类型的参数会自动转换为int类型。例如,对于下面的函数:
int isvowel (char c) {……}
因为其形参为char类型,所以在调用该函数的其他文件中必须声明:
int isvowel (char );
否则,调用者将把传递的实参自动转换为int类型,这样就与形参不一致了。如果函数是这样定义的:
int isvowel (int c) {……}
那么调用者就无需进行声明,即使调用者在调用时传递给函数一个char类型的参数也是如此。
4.5 检查外部类型
C语言中规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。
4.6 头文件