《C陷阱与缺陷》读书笔记4

 

第四章 连接

 

        因为编译器一般每次只处理一个文件,所以它不能检测出那些需要一次了解多个源文件才能察觉的错误。而且,在许多系统中连接器是独立于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  头文件

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值