假定我们有一个C程序,它由两个源文件组成。一个文件中包含外部变量n的声明:
extern int n;
另一个文件中包含外部变量n的定义:
long n;
这里假定两个语句都不在任何一个函数体里,因此n是外部变量。
这里一个无效的C程序,因为同一个外部变量名在两个不同的文件中被声明为不同的类型。然而大多数C语言实现却不能检测出这种错误。编译器对这两个不同的文件分别进行处理,这两个文件的编译时间甚至可以相差好几个月。因此,编译器在编译一个文件时,并不知道另一个文件的内容。连接器可能对C语言一无所知,因此它也不知道如何比较两个n的定义中的类型
当这个程序运行时,究竟会发生什么情况呢?存在很多的可能情况:
因此,保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。而且,“相同的类型”应该是严格意义上的相同。例如,考虑下面的程序,在一个文件中包含定义:
char filename[]=”/etc/passwd”;
而在另一个文件中包含声明:
extern char* filename;
尽管在某此上下文环境中,数组与指针非常类似,但它们毕竟不同。在第一个声明中,filename是一个字符数组的名称。尽管一个语句中引用filename的值将得到指向数组起始元素的指针,但是filename的类型是“字符数组”,而不是“字符指针”。在第二个声明中,filename被确定为一个指针。这两个对filename的声明使用存储空间方式是不同的;它们无法以一种合乎情况的方式共存。第一个例子中字符数组的内存布局大致如图4.1所示。
第二个例子中字符指针filename的内存布局大致如图4.2所示。
要更正本例,应该改变filename的声明或定义中的一个,使其与另一个类型匹配。因此,即可以是如下改法:
char filename[]=”/etc/passwd”;/*文件1*/ extern char filename[];/*文件2*/
也可以是这种改法:
char*filename=”/etc/passwd”/*文件1*/ extern char*filename;/*文件2*/
有关外部类型方面,另一种容易带来麻烦的方式是忽略了声明函数的返回类型,或者声明了错误的返回类型。例如,回顾一下我们在4.4节中讨论的程序:
main() { double s; s=sqrt(2); printf(“%g\n”, s); }
这个程序没有包括对函数sqrt的声明,因而函数sqrt的返回类型只能从上下文进行推断。C语言的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。因此这个程序完全等同于下面的程序:
extern int sqrt(); main() { double s; s=sqrt(2); printf(“%g\n”, s); }
当然,这种写法是错误的。函数sqrt返回双精度类型。因此,这个程序的结果是不可预测的。事实上,该程序似乎能够在某些机器上工作。举例来说,假定有这样一种机器,无论函数的返回值是整型值还是浮点值,它都使用了同样的寄存器。这样的计算机,将直接把函数sqrt的返回结果按其二进制表示的各个位传递给函数printf,而并不去检查类型是否一致。函数printf得到了正确的二进制表示,当然能够打印出正确的结果。某些机器在不同的寄存器中存储整数与指针。在这样的机器上,即使不牵涉到浮点运算,这种类型的错误也仍然可能造成程序失败。