【C圈套和缺陷】连接

【C陷阱和缺陷】连接

一,概念

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

         
二,连接过程问题的根源

        C程序 - > 预处理成demo.i - > 汇编成 demo.s - > 编译成 demo.o - > 连接成 demo可执行程序

        编译器为程序分配好了内存并翻译成了机器语言,而连接器了解c语言的内存分配和机器语言,所以连接器可以把各个模块组装起来。但是,连接器和编译器是分开的,即是说连接器并不清楚c语言的细节什么的;

         编译器一般一次只处理一个文件,不能检测出多个文件组合后的问题,这正是连接问题的主要来源。
 
三,重要概念:

        外部对象:程序中每个函数和每个外部变量,如果没有声明为static,就都是一个外部对象,分别称为全局函数和全局变量。

        通过static修饰的函数和变量,称为静态函数和静态变量。

        注:静态函数和静态变量在某些c编译器中也是外部对象,只是它们的名称会做一些改变,所以不会与其他源程序文件中的同名函数或同名变量发生命名冲突,可是在其它源文件中想直接引用该静态函数或变量时(extern *),也束手无策了。
 
四,连接器主要工作是:

        当出现同名的外部对象时,处理这类命名冲突。

           1)声明和定义:声明可以多个(通过extern *),但定义只能由一个,在一个文件中定义,其他文件中引用。

           2)命名冲突与static修饰符:static可以有效地消除命名冲突问题,如果一个源文件的某个对象如果不需要共享给其它源文件,最好用static修饰符把该对象限制为本文件可见。

           3)形参和实参:该部分主要讨论的行参和实参的类型匹配问题,主要是默认类型为int型,而一般只允许“大”类型兼容“小”类型,而试图用“小”类型兼容“大”类型则会出错。

           4)检查外部类型:指的是外部声明的类型要和定义类型一致。譬如指针和数组很多方面操作上一致的,  但是却是显然不同的两种类型。

           5)头文件:解决这些问题的重要方法之一,把外部的声明写在头文件中,引用这些外部变量时只需要 include该文件就行,在统一一个地方也方便出问题时进行修改。

 

检查外部类型举例:

            比如在两个文件中的extern int n;和long n;

             运行存在着很多可能情况:

                         1)编译器检测到冲突并返回诊断消息;

                         2)使用的C语言实现对int和long在内部表示上是一样的,很巧合地,可以正常工作;

                         3)二者虽然需要存储空间大小不同,但它们共享存储空间恰好可以保证赋给其中之一的值对另一个有效(比如低端部分共享存储空间),本来错误的程序因某种巧合却能正常工作,类似第二种情况;

           共享存储空间时给一个赋值却相当于对另一个赋予不同的值,不能正常工作。这样的引申例子:char filename[] = "/etc/passwd";和extern char* filename;,虽然前者的引用filename的值将得到指向该数组起始元素的指针,但filename类型是“字符数组”,而不是后者的“字符指针”,二者无法以一种合乎情理的方式共存。

/* 改法 1 */

char filename[] = "/etc/passwd"; /* 文件1 */

extern char filename[];                /* 文件2 */

/*  改法 2 */

char* filename = "/etc/passwd";  /* 文件1 */

extern char* filename;                /* 文件2 */

 
五,关于头文件的一个重要用法

        解决外部对象在哪声明的好方法是使用头文件

        头文件一般作为一个源程序文件(模块)与外界交互的地方,所以在头文件中一般放置了一些共享用的资源。譬如变量,结构类型等。其实头文件可以作为一个面向对象编程中提到的接口声明文件,把所有可能被调用的函数提取到头文件中,用extern type function(parameters),所有需要调用该文件(模块)中的函数时,只要包含该头文件即可。