为了使函数原型声明适用于旧式函数定义,两者都必须使用 ISO C 术语指定功能相同的接口或必须具有兼容类型。
对于具有可变参数的函数,不能混合 ISO C 的省略号和旧式 varargs() 函数定义。对于具有固定数目参数的函数,情况相当简单: 只需指定在先前实现中传递的参数的类型。
在 K&R C 中,根据缺省参数提升,就在将每个参数传递到被调用函数之前对其进行转换。这些提升规定,所有比 int 短的整数类型均要提升为 int 长度,并且任何 float 参数均要提升为 double,从而简化了编译器和库。函数原型更具有表现力-指定的参数类型即为传递给函数的类型。
因此,如果为现有的(旧式)函数定义编写函数原型,则在具有以下任一类型的函数原型中不应有参数:
char |
signed char |
unsigned char |
float |
short |
signed short |
unsigned short |
|
在编写原型方面仍存在两个复杂因素: typedef 名称以及短的无符号类型的提升规则。
如果旧式函数中的参数是使用 typedef 名称(如 off_t 和 ino_t)声明的,则知道 typedef 名称指定的类型是否受到缺省参数提升的影响是至关重要的。对于这两个名称,off_t 是 long 类型的,因此它适合于在函数原型中使用;ino_t 过去是 unsigned short 类型的,因此如果在原型中使用它,则编译器将发出诊断,因为旧式定义和原型指定的接口不同且不兼容。
考虑究竟应该使用什么来代替 unsigned short 致使问题最终复杂化。K&R C 和 1990 ISO C 编译器之间一个最大的不兼容性是用于将 unsigned char 和 unsigned short 展宽为 int 值的提升规则。(请参见6.4 提升:无符号保留与值保留。)与这样的旧式参数匹配的参数类型取决于编译时使用的编译模式:
-Xs 和 –Xt 应使用 unsigned int
–Xa 和 –Xc 应使用 int
最佳方法是将旧式定义更改为指定 int 或 unsigned int 并使用函数原型中的匹配类型。如有必要,在输入函数后,您可以始终将其值赋给具有更窄类型的局部变量。
请注意原型中 ID 的使用,它可能受预处理的影响。请看以下示例:
#define status 23 void my_exit(int status); /* Normally, scope begins */ /* and ends with prototype */ |
不要将函数原型与包含窄类型的旧式函数声明混合在一起。
void foo(unsigned char, unsigned short); void foo(i, j) unsigned char i; unsigned short j; {...} |
正确使用 __STDC__ 可生成一个可用于新旧编译器的头文件:
header.h: struct s { /* . . . */ }; #ifdef __STDC__ void errmsg(int, ...); struct s *f(const char *); int g(void); #else void errmsg(); struct s *f(); int g(); #endif |
以下函数使用原型,但仍可在较旧的系统中编译:
struct s * #ifdef __STDC__ f(const char *p) #else f(p) char *p; #endif { /* . . . */ } |
下面是更新的源文件(与上述选项 3 相同)。局部函数仍使用旧式定义,但包括了原型供较新的编译器使用:
source.c: #include “header.h” typedef /* . . . */ MyType; #ifdef __STDC__ static void del(MyType *); /* . . . */ static void del(p) MyType *p; { /* . . . */ } /* . . . */ |