今天抽时间看了看第四章。
1.什么是连接器。
编译器的责任是把C源程序“翻译”成对连接器有意义的形式,这样连接器就能“读懂”
C语言程序了。
典型的连接器把由编译器或汇编器生成的若干个目标模块整合成一个被称为载入模块
会可执行文件的实体,该实体能够被操作系统直接执行。其中,有些目标模块是直接作为输
入直接提供给连接器;另一些目标模块则是根据连接过程的需要,从库文件中提取。
连接器通常把目标模块看成是由一组外部对象组成的。每个外部对象代表着机器内存中
的某部分,并通过一个外部名称来识别。
2.声明和定义
对一个变量进行声明后就会为其分配内存,如果有初始值那么就说明该内存中储存的值。
如果声明前面有关键字extren,那么就是显示的将这个变量声明为外部变量,因此,要在某
个程序的某个位置有一个声明。例如声明了extern int a;,那么在某个程序的某个位置就有
int a;。为了避免麻烦,所有的变量名不要重复。
3.命名冲突和static修饰符
两个具有相同名称的外部对象实际上代表的是同一个对象,即使编程者的本意并非如此,
但系统却会这样处理,即使定义是出现在库文件中。但ANIS C标准函数库中列出了常常用
到的可能引发命名冲突的所有函数。
static修饰符是一个能够减少此类命名冲突的有用工具。例如:
static int a;
与
int a;
同样是定义了一个变量a,但是前一个变量a只用于定义它的文件,对于其他文件它是不可
见的。static同样可以用在函数上。
因此,如果为了避免此类命名冲突,但一个函数或变量仅仅被同一个源文件中的函数调
用时,我们就应该将之声明为static。
4.形参实参与返回值
每个函数都有形参列表,可能为空;每个函数也有返回值,可能为void或者是函数生
成结果的类型。
先讨论返回值。
一个函数如果在调用它的每个文件中,都在第一次调用之前进行了声明或定义,那么将
不会出现任何有关返回值的问题了。即使从不同的文件中调用已定义的函数,同样需要在调
用前进行声明,或者是通过头文件来调用。
再来讨论形参与实参。
在函数声明时可以省略形参,甚至整个参数列表,但是在定义时却不行。这样做依赖于
调用者提供数目正确并且类型恰当的实参。但是“恰当”并不等于“等同”:float会自动转
换成double,char会自动转换成int。
为了与早期的用法兼容,ANIS C也支持较“老”的声明和定义。
因为函数printf和函数scanf在不同的情况下会接受不同类型的参数,所以它们特别容
易出错。
5.检查外部类型
如果一个文件中包含了long int n;,另一个文件中包含了int n;,假定这两个语句不再任
何一个函数体中,因此n是一个外部变量,那么这是一个无效的C程序,但是大多数C语
言实现却不能检测出来。那么这个程序运行时会发生什么呢?
1.C编译器足够“聪明”,能够检测出来这一类型冲突
2.读者使用的C语言实现对int类型的数值与long类型的数值在内部表示上是一样的
3.变量n的两个实例虽然要求的空间的大小不同,但是它们共享存储空间,且这种方式
恰好满足这样的条件:赋值给其中一个的值,另一个也是有效的
4. 变量n的两个实例共享存储空间,但是给一个赋值时,其效果是相当于给另一个赋
了完全不同的值
有关外部类型方面,另一个容易带来麻烦的方式是忽略了声明函数的返回类型或声明了
错误的返回类型。在C语言中规定,如果一个未声明的标识符后跟一个开括号,那么它将被
视为一个返回值类型为整型的函数。
因此,保证一个特定名称的所有外部定义在每个目标文件中都有相同的类型是程序员的
责任,而且这种“相同的类型”应该是严格意义上的相同。例如数组和指针,虽然非常相似,
但是它们之间还是不同的。
6.头文件
有一个方法可以解决大部分这类问题,这个方法只需要我们接受一个规则:每个外部对
象只在一个地方声明了,而这个地方就是头文件。需要用到该外部对象的模块都应该包括这
个头文件,特别需要指出的是,定义该外部对象的模块也应该包括这个头文件。
这是第四章。