C函数语法,参数类型在参数列表后声明。

93

我对C语言还比较陌生。我遇到了一种从未见过的函数语法形式,其中参数类型是在参数列表之后定义的。有人能解释一下它与典型的C函数语法有何不同吗?

示例:

int main (argc, argv)
int argc;
char *argv[];
{
return(0);
}
7个回答

73

这是旧式参数列表的语法,目前仍然支持。在K&R C中,您也可以省略类型声明,它们会默认为int。也就是说:

main(argc, argv)
char *argv[];
{
    return 0;
}

将是相同的函数。


19
C89/90和C99仍然官方支持K&R样式的声明。然而,在C99中,所有参数都必须显式声明(不再有"implicit int"规则)。 - AnT stands with Russia
1
我不想迟到(四年后?)才开始参与讨论,不过您的意思是在 C99 中它们需要明确地在参数列表中声明还是在函数中声明?例如,默认的 int 规则 - 由于它们不再默认为 int,因此它们的类型在使用之前需要在函数中声明,如果这些类型没有在参数列表中指定的话? - Chris Cirefice
4
回答你四年后的问题:在C99中,所有参数必须在参数列表中声明。在函数中声明的变量从未默认为 "int",因此必须明确声明。 - Frank Kusters

29
有趣的是带有原型和没有原型的函数的调用约定差异。考虑一种旧式定义:
void f(a)
 float a; {
 /* ... */
}

在这种情况下,调用约定是在将所有参数传递给函数之前进行提升(例如,一个float参数首先被提升为double,然后再传递)。因此,如果f接收到一个double,但参数的类型是float(这是完全有效的),编译器必须发出将double转换为float的代码,然后执行函数体。
如果包含原型,编译器不再进行自动提升,并且传递的任何数据都会按照赋值方式转换为原型参数的类型。所以下面的代码是不合法的,并且会导致未定义的行为:
void f(float a);
void f(a)
  float a; {

}

在这种情况下,函数的定义将把提交的参数从double(提升形式)转换为float,因为该定义是旧式的。但是,由于函数具有原型,因此参数被提交为浮点数。例如,clang给出以下警告:
main.c:3:9:警告:K&R函数参数的提升类型“double”与先前原型中声明的参数类型“float”不兼容[-Wknr-promoted-parameter]
解决矛盾的两种选择如下:
// option 1
void f(double a);
void f(a)
  float a; {

}

// option 2
// this declaration can be put in a header, but is redundant in this case, 
// since the definition exposes a prototype already if both appear in a 
// translation unit prior to the call. 
void f(float a); 

void f(float a) {

}

如果你有选择的话,应该优先选择Option 2,因为它消除了旧式定义。如果在同一翻译单元中出现了函数的不一致功能类型,编译器通常会告诉你(但不是必须的)。如果这种矛盾出现在多个翻译单元中,错误可能会被忽略,并导致难以预测的错误。最好避免使用这些旧式定义。

11

这是所谓的K&R风格旧式声明。

请注意,这种声明与现代声明显著不同。K&R声明不会为函数引入原型,也就是说它不会将参数类型暴露给外部代码。


7

其实它们没有区别,只是这是C语言中旧版本函数声明的语法,也就是ANSI标准之前使用的。但是请不要编写此类代码,除非你打算送给80年代的朋友们。此外,永远不要依赖于隐式类型假设(正如另一个答案所建议的那样)。


在C99之前,它被用于ANSI标准化之前的时期,该标准于1989年被规范化,但该过程始于1983年。 - Brian Campbell
2
啊嗯...K&R风格的声明在C99中仍然被官方支持,只有一个小变化 - 不再有"implicit int",所有参数都必须显式声明。 - AnT stands with Russia
在批评声中,我必须说我同意你的观点:不要这样做。 - BobbyShaftoe

5

虽然旧的函数定义语法仍然可以使用(如果您询问编译器,会有警告),但使用它们不提供函数原型。
没有函数原型,编译器将无法检查函数是否被正确调用。

#include <stdio.h>
int foo(c)
int c;
{ return printf("%d\n", c); }

int bar(x)
double x;
{ return printf("%f\n", x); }

int main(void)
{
    foo(42); /* ok */
    bar(42); /* oops ... 42 here is an `int`, but `bar()` "expects" a `double` */
    return 0;
}

当程序运行时,在我的机器上的输出结果为:
$ gcc proto.c
$ gcc -Wstrict-prototypes proto.c
proto.c:4: warning: function declaration isn’t a prototype
proto.c:10: warning: function declaration isn’t a prototype
$ ./a.out
42
0.000000

2

这只是同样的东西,但已经过时了。你可能会发现它是一些旧的、遗留的代码。


-1

无论新旧,我认为什么是老的,什么不是老的都可以争论。比如金字塔是古代的,但今天的所谓科学家们都不知道它们是如何建造的。回顾过去,旧程序仍然能够在今天正常运行而不会出现内存泄漏,但这些“新”程序往往更容易失败。我看到了一个趋势。

也许他们将函数视为具有可执行体的结构体。需要了解汇编语言才能解决这个谜团。

编辑,发现一个宏,表明您根本不需要提供参数名称。

#ifndef OF /* function prototypes */
#  ifdef STDC
#    define OF(args)  args
#  else
#    define OF(args)  ()
#  endif
#endif

#ifndef Z_ARG /* function prototypes for stdarg */
#  if defined(STDC) || defined(Z_HAVE_STDARG_H)
#    define Z_ARG(args)  args
#  else
#    define Z_ARG(args)  ()
#  endif
#endif

这里是一个使用示例,库文件是zlib-1.2.11

ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));

所以我的第二个猜测是函数重载,否则这些参数就没有用处。一个具体的函数,现在有无限数量的同名函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接