9.2.1 产生的问题
下面我们讨论几个使用imax()函数的例子,该函数和imin()类似。在程序清单9.4中的程序以旧的形式声明函数imax(),然后错误的使用该函数。
程序清单9.4 misuse.c程序
/*misuse.c --不正确的使用函数*/
#include <stdio.h>
int imax(); /*旧式的函数声明*/
int main(void)
{
printf("The maximum of %d and %d is %d.\n",
3,5,imax(3));
printf("The maximum of %d and %d is %d.\n",
3,5,imax(3.0,5.0));
return 0;
}
int imax(n,m)
int n,m;
{
int max;
if(n>m)
max=n;
else
max=m;
return max;
}
在第一个printf()中调用函数imax()时漏掉了一个参数,而在第二次调用imax()时使用了浮点参数而不是整数参数。尽管存在这些错误,该程序仍可以编译执行。
程序运行时发生了什么?不同操作系统的内部机制不同,所以出现错误的具体情况也不相同。当使用PC或VAX时,程序执行过程是这样的:调用函数首先把参数放在一个被称为堆栈(stack)的临时存储区域里,然后被调函数从堆栈中读取这此参数。但是这两个过程并没有相互协调进行。调用函数根据调用过程中实际参数类型确定需要传递的数值类型,但是被调函数是根据其形式参数的类型进行数据读取的。因此,函数调用 imax(3)把一个整数放在堆栈中。当函数imax()开始执行时,它会从堆栈中读取两个整数。而实际上只有一个需要的数值被存储在堆栈中,所以第二个读取的数据就是当时恰好在堆栈中的其他数值。
第二次使用函数imax()时,传递的是float类型的数值。这时两个double类型的数值就被放在堆栈中(回忆一下,作为参数传递时float类型数据会被转换成double类型数据)。而在我们使用的系统中,这意味着两个64位的数值,即共128位的数据存储在堆栈中。因为这个系统中int系统是32位,所以当imax()从堆栈中读取两个int类型的数值时,它会读取出堆栈中前面64位的数据,把这些数据对应于两个整数,其中较大的一个就是1074266112。
9.2.2 ANSI的解决方案
针对以上的参数错误匹配问题,ANSI标准的解决方案是在函数声明中同时说明所使用的参数类型。即使用函数原型(function prototype)来声明返回值类型、参数个数以及各参数的类型。为了表示imax()需要两个int类型的参数,可以使用下面原型中的任意一个进行声明:
int imax(int ,int);
int imax(int a,int b);
第一种形式使用逗号对参数类型进行分隔;而第二种形式在类型后加入了变量名。需要注意的是这此变量名只是虚拟的名字,它们不必和函数定义中使用的变量名相匹配。
使用这种函数原型信息,编译器就可以检查函数调用语句是否和其原型声明一致。比如检查参数个数是否正确,参数类型是否匹配。如果有一个参数类型不匹配但都是数值类型,编译器会把实际参数值转换成和形式参数类型相同的数值。例如 ,会把imax(3.0,5.0)换成imax(3,5).
当使用函数原型时,上例中的程序清单9.4会变成如下程序清单9.5。
程序清单9.5 proto.c程序
/*misuse.c --使用函数原型*/
#include <stdio.h>
int imax(int,int); /*原型*/
int main(void)
{
printf("The maximum of %d and %d is %d.\n",
3,5,imax(3));
printf("The maximum of %d and %d is %d.\n",
3,5,imax(3.0,5.0));
return 0;
}
int imax(int n,int m)
{
int max;
if(n>m)
max=n;
else
max=m;
return max;
}
当编译程序清单9.5时,编译器会给出一个错误信息,声称调用函数imax()时传递的参数太少。我们用imax(3,5)代替imax(3)后重新进行编译。这一次并没有出现任何错误信息。
虽然编译中没有出现错误信息,但是编译器给出了一条警告信息,提示doube类型数据被转换成了int类型的数据,因此可能会损失数据。例如,以下函数调用:
imax(3.9,5.4);等价于语句imax(3,5);
错误和警告的不同之处在于前者阻止了编译的继续而后者不阻止。
9.2.3 无参数和不确定参数
假设使用以下函数原型:
void printf_name();
这时一个ANSI C 编译器会假设您没有用函数原型声明函数,它就不会进行参数检查。因此,为了表示一个函数确实不使用参数,需要在圆括号内加入void关键字:
void printf_name(void);
ANSI C 会把上句解释为pintf_name()不接受任何参数,因此当对函数进行调用时编译器就会检查以保证您确实没有使用参数。一些函数使用的参数个数是变化的。例如,在printf()中,第一个参数是一个字符串,而其余参数的类型以及参数个数并不固定。对于这种情况,ANSI C 允许使用不确定的函数原型。例如,对于printf()可以使用下面的原型声明:
int printf(char *,...);
这种原型表示第一个参数是一个字符串,而其余参数不能确定。
对于参数个数不确定的函数,C库通过stdarg.h头文件提供了定义该类函数的标准方法。第16章“C预处理器和C库”详细讲述了有关内容。
9.2.4 函数原型的优点
函数原型是对语言的有力补充。它可以使编译器发现函数使用时可能出现的错误或疏漏。
有一种方法可以不使用函数原型却保留函数原型的优点。之所以使用函数原型,是为了在编译器编译第一个调用函数的语句之前向其表明该函数的使用方法。因此,可以在首次调用某函数之前对该函数进行完整的定义。这样函数定义部分就和函数原型有着相同的作用。通常对于较小的函数会这样做:
//下面即是一个函数定义,也是它的原型
int imax(int a,int b) { return a>b ? a:b;}
int main(void)
{
...
z=imax(x,50);
...
}
来源:oschina
链接:https://my.oschina.net/u/2754880/blog/709144