C陷阱与缺陷之检查外部类型

假定我们有一个C程序,它由两个源文件组成。一个文件中包含外部变量n的声明:

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

这里假定两个语句都不在任何一个函数体里,因此n是外部变量。

这里一个无效的C程序,因为同一个外部变量名在两个不同的文件中被声明为不同的类型。然而大多数C语言实现却不能检测出这种错误。编译器对这两个不同的文件分别进行处理,这两个文件的编译时间甚至可以相差好几个月。因此,编译器在编译一个文件时,并不知道另一个文件的内容。连接器可能对C语言一无所知,因此它也不知道如何比较两个n的定义中的类型

当这个程序运行时,究竟会发生什么情况呢?存在很多的可能情况:

  1. C语言编译器足够“聪明”,能够检测到这一类型冲突。编程者将会得到一条诊断消息,报告变量n在两个不同的文件中被给定了不同的类型。
  2. 读者使用的C语言实现对int类型的数值与long类型的数值在内部表示上是一样的。尤其是在32位计算机上,一般都是如此处理。在这种情况下,程序很可能正常工作,就好像n在两个文件中被都声明为long(或int)类型一样。本来错误的程序因为某种巧合却能够工作,这是一个很好的例子。
  3. 变量n的两个实例虽然要求的存储空间的大小不同,但是它们的共享存储空间的方式却恰好能满足这样的条件:赋给其中一个值,对另一个也是有效的,这是有可能发生的。举例来说,如果连接器安排int类型的n与long类型的n低端部分共享存储空间,这样给每个long类型的n赋值,恰好相当于其低端部分赋给了int类型的n。本来错误的程序因为某种巧合却能够工作,这是一个比第2种情况更能说明问题的例子。
  4. 变量n的两个实例共享存储空间的方式,使得对其中一个赋值时,其效果相当于同时给另一个赋了完全不同的值。在这种情况下,程序将不能正常工作。

因此,保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。而且,“相同的类型”应该是严格意义上的相同。例如,考虑下面的程序,在一个文件中包含定义:

而在另一个文件中包含声明:

尽管在某此上下文环境中,数组与指针非常类似,但它们毕竟不同。在第一个声明中,filename是一个字符数组的名称。尽管一个语句中引用filename的值将得到指向数组起始元素的指针,但是filename的类型是“字符数组”,而不是“字符指针”。在第二个声明中,filename被确定为一个指针。这两个对filename的声明使用存储空间方式是不同的;它们无法以一种合乎情况的方式共存。第一个例子中字符数组的内存布局大致如图4.1所示。

第二个例子中字符指针filename的内存布局大致如图4.2所示。

要更正本例,应该改变filename的声明或定义中的一个,使其与另一个类型匹配。因此,即可以是如下改法:

也可以是这种改法:

有关外部类型方面,另一种容易带来麻烦的方式是忽略了声明函数的返回类型,或者声明了错误的返回类型。例如,回顾一下我们在4.4节中讨论的程序:

这个程序没有包括对函数sqrt的声明,因而函数sqrt的返回类型只能从上下文进行推断。C语言的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。因此这个程序完全等同于下面的程序:

当然,这种写法是错误的。函数sqrt返回双精度类型。因此,这个程序的结果是不可预测的。事实上,该程序似乎能够在某些机器上工作。举例来说,假定有这样一种机器,无论函数的返回值是整型值还是浮点值,它都使用了同样的寄存器。这样的计算机,将直接把函数sqrt的返回结果按其二进制表示的各个位传递给函数printf,而并不去检查类型是否一致。函数printf得到了正确的二进制表示,当然能够打印出正确的结果。某些机器在不同的寄存器中存储整数与指针。在这样的机器上,即使不牵涉到浮点运算,这种类型的错误也仍然可能造成程序失败。

未经允许不得转载:TacuLee » C陷阱与缺陷之检查外部类型

赞 (0)

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址