C陷阱与缺陷——第4章连接

一个C程序可能是由多个分别编译的部分组成,这些不同部分通过一个通常叫做连接器(也叫连接编辑器、或载入器)的程序合并成一个整体。因为编辑器一般每次只处理一个文件,所以它不能检测出那些需要一次了解多个源程序文件才能察觉的错误。在许多系统中连接器是独立于C语言实现的,因此如果前述错误的原因是与C语言相关的,连接器对此同样束手无策。

1. 什么是连接器

C语言的一个重要思想就是分别编译,即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合在一起,尽管连接器并不理解C语言,然而它却能够理解机器语言和内存布局。编译器的责任是把C源程序翻译成对连接器有意义的形式,这样连接器就能够读懂C源程序了。

典型的连接器是把由编译器或者汇编器生成的若干个目标模块,整合成一个被称为载入模块或者可执行文件的实体,该实体能够被操作系统直接执行。某些目标模块是直接作为输入提供给连接器的,而另外一些目标模块则是根据连接过程的需要,从包括类似printf函数的库文件中取得的。

连接器通常被目标模块看成是由一组外部对象组成的。程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部对象。

大多数连接器都禁止同一个载入模块中的两个不同外部对象拥有相同的名称,然而,在多个目标模块整合成一个载入模块时,这些目标模块可能就包含了同名的外部变量,连接器的一个重要工作就是处理这类命名冲突。

处理命名冲突最简单的办法是完全禁止,对于外部对象是函数的情况,这种做法当然正确;但是对于外部对象是变量的情况,问题就变得有些困难了。

除了外部对象外,模板模块中还可能包括了对其他模块中外部对象的引用,比如调用了printf函数的c程序所生成的目标模块就包含了一个对函数printf的引用。

2. 声明和定义

如声明语句

int a;

如果其位置出现在所有函数体之外,那么它就被称为外部对象a的定义,初始值默认为0。

extern int a;

以上的声明并不是定义,这个语句说明了a是一个外部整型变量,包含extern关键字说明a的存储空间是在程序的其他地方分配的。

如果在不同文件中分别存在以下定义会怎么样

int a = 7;

另一个文件中

int a = 9;

行为和操作系统有关,严格的规则是每个外部变量只能够定义一次,系统会拒绝这个行为。

3. 命令冲突与static修饰符

如果一个库函数需要调用另一个未在ANSI C标准中列出的函数,它应该以隐藏名称的方式来调用后者

static修饰符能够减少命名冲突,以下语句

static int a;

a的作用域限制在一个源文件内,对其他源文件不可见。

static修饰符不仅适用于变量,也实用于函数。

为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,我们就应该声明该函数为static

4. 形参、实参与返回值

任何C函数都有一个形参列表,列表中的每个参数都是一个变量,该变量在函数调用过程中被初始化。

如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或定义,那么就不会有任何与返回类型相关的麻烦。

如果一个函数在被定义或声明之前被调用,那么它的返回值类型默认就是整型。

ANSI C允许程序员在声明时指定函数的参数类型。

因为函数printf与函数scanf在不同情形下可以接受不同类型的参数,所以它们特别容易出错,如下:

include 
main()
{
    int i;
    char c;
    for (i = 0; i < 5; i++)
    {
        scanf("%d", &c);//这里char c应该改成int c或者%d改成%c
        printf("%d ", i);
    }
    return 0;
}

这里c被声明为char类型,而不是int类型,当程序要求scanf读入一个整数,应该传递给它一个指向整数的指针,而程序中scanf函数得到的却是一个指向字符的指针,但是scanf不能分辨这种情况,它只是将c当作指向整型的指针接受,并且在指针指向的位置存储一个整数,因为整数所占的存储空间大于字符所占的空间,因此字符c附近的内存将被覆盖

输入如下:

0 1 2 3 4

可能的输出结果为:

0 0 0 0 0 1 2 3 4

5. 检查外部类型

假设一个文件中有如下声明:

extern int n;

另一个文件中包含外部变量n的定义:

long n;

这是一个无效的C程序,因为同一个外部变量名在两个不同的文件中被声明为不同的类型,然而,大多数C语言实现却不能检测出这种错误。

保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任,而且,相同的类型应该是严格意义的相同。

有关外部类型方面,另一种容易带来麻烦的方式是忽略了声明函数的返回类型,或者声明了错误的返回值类型。

6. 头文件

每个外部对象只在一个地方声明,这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件,特别需要指出的是,定义该外部对象的模块也应该包括这个头文件。

你可能感兴趣的:(c语言,c语言,开发语言)