函数声明与原型的替代(K&R)C语法


76

C使用'K&R'样式函数声明,此语法有什么用?

int func (p, p2)
    void* p;
    int  p2;
{
    return 0;
}

我能够在Visual Studios 2010beta中编写此代码

// yes, the arguments are flipped
void f()
{
    void* v = 0;
    func(5, v);
}

我不明白 这种语法有什么意义?我可以写:

int func (p, p2)
    int  p2;
{
    return 0;
}
// and write
int func (p, p2)
{
    return 0;
}

它似乎唯一要指定的是它使用了多少个参数以及返回类型。我猜没有类型的参数有点酷,但是为什么要允许它以及int paranName函数声明符之后呢?有点奇怪。

还是这个标准C吗?


Answers:


153

您要问的问题实际上是两个问题,而不是一个。到目前为止,大多数答复都试图用通用毯子“这就是K&R风格”覆盖整个问题,而实际上,只有一小部分与所谓的K&R风格有关(除非您看到整个C语言)作为一种“ K&R风格” :)

第一部分是函数定义中使用的奇怪语法

int func(p, p2)
void *p;
int  p2; /* <- optional in C89/90, but not in C99 */
{
  return 0;
}

这实际上是一种K&R样式的函数定义。其他答案已经很好地涵盖了这一点。实际上,它没有太多。该语法已被弃用,但即使在C99中也仍然完全受支持(C99中的“无隐式int”规则除外,这意味着在C99中您不能省略的声明p2)。

第二部分与K&R风格无关。我指的是可以使用“交换”参数调用函数的事实,即在这种调用中不会进行参数类型检查。这本身与K&R样式的定义几乎没有关系,但是与没有原型的函数有关。您会在C中声明这样的函数时看到

int foo();

它实际上声明了一个函数foo,该函数接受未指定数量的未知类型的参数。您可以将其称为

foo(2, 3);

并作为

j = foo(p, -3, "hello world");

回答等等(你明白了);

只有带有适当参数的调用才会“起作用”(意味着其他调用会产生未定义的行为),但这完全取决于您自己的正确性。即使编译器以某种方式神奇地知道了正确的参数类型及其总数,也不需要编译器诊断出错误的参数。

实际上,此行为是C语言的功能。一个危险的,但是一个功能。它可以让你做这样的事情

void foo(int i);
void bar(char *a, double b);
void baz(void);

int main()
{
  void (*fn[])() = { foo, bar, baz };
  fn[0](5);
  fn[1]("abc", 1.0);
  fn[2]();
}

也就是说,在没有任何类型转换的情况下将不同的函数类型混合在“多态”数组中(尽管此处不能使用变量函数类型)。同样,此技术的固有危险非常明显(我不记得曾经使用过它,但我可以想象它在什么地方有用),但这毕竟是C。

最后,将答案的第二部分链接到第一部分的位。进行K&R样式的函数定义时,它不会引入该函数的原型。就函数类型而言,您的func定义声明func

int func();

即既不声明类型也不声明参数总数。在您的原始帖子中,您说“ ...似乎指定了它使用了多少个参数...”。正式而言,事实并非如此!在用两参数K&R样式func定义之后,您仍然可以将其func称为

func(1, 2, 3, 4, "Hi!");

并且不会有任何约束冲突。(通常,高质量的编译器会警告您)。

另外,有时被忽视的事实是

int f()
{
  return 0;
}

也是未引入原型的K&R样式函数定义。要使其“现代”,您必须void在参数列表中放置一个明确的名称

int f(void)
{
  return 0;
}

最后,与普遍的看法相反,C99完全支持K&R风格的函数定义和非原型函数声明。如果我没记错的话,自C89 / 90起就不推荐使用前者了。C99要求在首次使用之前声明该函数,但声明该声明不是原型。这种混淆显然源于流行的术语混淆:许多人称任何函数声明为“原型”,而实际上“函数声明”与“原型”并不相同。


1
但是,现在varargs函数和未原型函数之间有什么区别。
塞巴斯蒂安

1
@Sebastian Godelet varargs严格要求严格。例如,使用一个int调用vararg函数并将其作为字节弹出是无效的。因此,仅使用vararg参数定义函数。
yyny

实际上,可以使用不同数量的参数正确调用@Sebastian Vararg函数,例如printf。没有原型的函数只能使用一定数量的固定参数正确调用,但是编译器不知道哪个且无法检查(因此必须执行此操作)。
不是用户的用户,

我想是时候改变了supported even in C99C99 to C11。:-)。
sjsam

我记得当我在Amstrad PCW上开始使用C时,我很困惑,因为编译器使用了旧的函数声明语法,但是我所使用的教程书却使用了较新的语法。那是25年前的日子!
安德鲁·特拉

18

这是相当古老的K&R C语法(早于ANSI / ISO C)。如今,您不应再使用它了(因为您已经注意到它的主要缺点:编译器不会为您检查参数的类型)。int在您的示例中,参数类型实际上默认为。

当时使用的是这种语法,有时会发现类似

foo(p, q) 
{
    return q + p;
}

这实际上是一个有效的定义,作为类型foopq默认int


5

这只是一种古老的语法,早于您可能更熟悉的“ ANSI C”语法。通常称为“ K&R C ”。

当然,编译器支持它完整,并能够处理旧代码库。


2

那是C没有函数原型时的遗物。当时,我认为函数返回int并且所有参数都假定为int。没有检查功能参数。

在当前的C语言中使用函数原型会更好。
而且您必须在C99中使用它们(C89仍接受旧语法)。

C99需要声明函数(可能没有原型)。如果您是从头开始编写新函数,则需要提供一个声明...也使其成为原型:您将一无所获,并从编译器中获得额外的检查。


4
不正确 在C99中,必须在调用函数之前明确声明它们。没关系,C99不需要原型声明。C99进行了一项更改,完全支持K&R语法:隐式int规则已删除。
AnT

1

这是1989年C标准化之前的原始K&R语法。C89引入了从C ++借用的函数原型,并弃用了K&R语法。没有理由在新代码中使用它(并且有很多理由不这样做)。

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.