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

C function syntax, parameter types declared after parameter list

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

例子:

1
2
3
4
5
6
int main (argc, argv)
int argc;
char *argv[];
{
return(0);
}

这是参数列表的旧样式语法,仍然受支持。在K&R C中,您也可以不使用类型声明,它们将默认为int。

1
2
3
4
5
main(argc, argv)
char *argv[];
{
    return 0;
}

将具有相同的功能。


还有一个有趣的地方是函数与不带原型的函数之间的调用约定差异。考虑旧的样式定义:

1
2
3
4
void f(a)
 float a; {
 /* ... */
}

在这种情况下,调用约定是在传递给函数之前提升所有参数。因此,如果f接收到double但参数的类型为float(这是完全有效的),编译器必须在执行函数体之前发出代码,将double转换为float。

如果包含原型,编译器将不再执行此类自动升级,并且传递的任何数据都将转换为原型的参数类型,就像通过赋值一样。因此,以下行为不合法,导致行为不明确:

1
2
3
4
5
void f(float a);
void f(a)
  float a; {

}

在这种情况下,函数的定义会将提交的参数从double(提升形式)转换为float,因为该定义是旧样式。但是参数是作为一个浮点提交的,因为函数有一个原型。解决矛盾的方法有以下两种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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应该是首选的,因为它会预先去掉旧的样式定义。如果函数的这种矛盾函数类型出现在同一翻译单元中,编译器通常会告诉您(但不是必需的)。如果这样的矛盾出现在多个翻译单元上,那么错误可能会被忽略,并且很难预测错误。最好避免使用这些旧的样式定义。


这是SO调用者k&r样式或旧样式声明。

请注意,本宣言与现代宣言有显著不同。K&R声明不会为函数引入原型,这意味着它不会向外部代码公开参数类型。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int foo(c)
int c;
{ return printf("%d
"
, c); }

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

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

当程序运行时,我机器上的输出是

1
2
3
4
5
6
7
$ 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

没有区别,只是这是C语言中函数声明的旧语法——它是在ANSI之前使用的。不要写这样的代码,除非你打算把它交给80年代的朋友。同样,不要依赖于隐含的类型假设(另一个答案似乎建议)


这只是一种古老的时尚。您可能发现它是一些旧的、遗留的代码。


不管老与否,我都会争论什么是旧,什么是新……就像金字塔是古老的,但今天所谓的科学家都不知道它们是如何在哪里形成的。回顾过去,旧的程序在今天仍然可以正常工作,没有内存泄漏,但是这些"新"程序往往比以前失败得多。我在这里看到了一种趋势。

可能他们将函数看作具有可执行体的结构。这里需要掌握ASM的知识来解开这个谜。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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。

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

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