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


78

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

例:

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

Answers:


65

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

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

将具有相同的功能。


17
C89 / 90和C99仍正式支持K&R样式声明。但是,在C99中,必须显式声明所有参数(不再有“隐式int”规则)。
AnT

1
我不想迟到派对(比如4年后?),但是您是说在C99中需要在参数列表函数中明确声明它们吗?例如,默认的int规则-由于它们不再默认为int,那么如果在参数列表中未提供类型,则需要在使用前在函数中声明其类型?
克里斯·西里菲斯

1
@ChrisCirefice要在4年后回答您:在C99中,所有参数都必须在参数列表中声明。在函数中声明的变量从未默认为int,因此必须显式声明。
Frank Kusters,

28

有趣的是,带有原型的函数和没有原型的函数的调用约定区别。考虑一个旧样式的定义:

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) {

}

如果可以选择的话,应该首选选项2,因为它可以预先消除旧样式的定义。如果某个函数的此类矛盾函数类型出现在同一转换单元中,则编译器通常会告诉您(但不是必需的)。如果这些矛盾出现在多个翻译单元上,则该错误可能会被忽略,并可能导致难以预测的错误。最好避免使用这些旧样式定义。


10

这就是所谓的K&R样式旧式声明。

注意,此声明与现代声明有很大不同。K&R声明没有引入该函数的原型,这意味着它不会向外部代码公开参数的类型。


4

没什么不同,只是这是C中函数声明的旧语法-它在ANSI之前使用。除非您打算将其提供给80年代的朋友,否则请不要编写此类代码。而且,永远不要依赖隐式类型假设(正如另一个答案似乎暗示的那样)


C99之前的版本;它使用的是ANSI之前的标准,该标准于1989年标准化,但该过程始于1983
。– Brian Campbell,2009年

2
哎... C99仍正式支持K&R样式声明,只做了一个小的更改-不再有“隐式int”,所有参数都必须显式声明。
AnT

在批评之中,我必须说我同意你的看法:不要这样做。
BobbyShaftoe

4

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

#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

1

它是相同的,但旧的时尚。您可能发现它是一些旧的遗留代码。


0

不论金字塔的古老与否,我都会争论,金字塔是古老的,还是古老的,但是今天所谓的科学家都不知道它们是如何制造的。回顾过去,旧程序在今天仍然可以正常工作,而不会发生内存泄漏,但是这些“新”程序往往会失败的频率更高。我在这里看到趋势。

他们可能将函数视为具有可执行主体的结构。这里需要了解ASM才能解决这个问题。

编辑,找到一个宏,该宏指示您根本不需要提供参数名称。

#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));

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.