【问题标题】:Alternative (K&R) C syntax for function declaration versus prototypes函数声明与原型的替代 (K&R) C 语法
【发布时间】:2021-10-17 20:24:57
【问题描述】:

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 吗?

【问题讨论】:

标签: c function function-declaration kernighan-and-ritchie function-definition


【解决方案1】:

您提出的问题实际上是两个问题,而不是一个。到目前为止,大多数回复都试图用一个通用的毯子“这是 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 as

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

并且不会有任何违反约束的情况。 (通常,高质量的编译器会给你一个警告)。

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

int f()
{
  return 0;
}

也是一个不引入原型的 K&R 风格的函数定义。要使其“现代”,您必须在参数列表中明确添加 void

int f(void)
{
  return 0;
}

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

【讨论】:

  • 但是现在可变参数函数和非原型函数之间有什么区别。
  • @Sebastian Godelet 可变参数过于严格。例如,使用一个 int 调用 vararg 函数,并将其作为字节弹出是无效的。定义一个只有可变参数的函数也是如此。
  • @Sebastian Vararg 函数实际上可以正确调用不同数量的参数,例如printf。没有原型的函数只有在一定数量的参数下才能正确调用,但编译器不知道是哪一个也无法检查(所以你必须这样做)。
  • 我想是时候改变 supported even in C99 了。 C99 to C11。 :-)。
  • 我记得当我在 Amstrad PCW 上开始使用 C 时,我感到很困惑,因为编译器使用的是旧的函数声明语法,但我的教程书使用的是较新的语法。那是……25年前的日子……!
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      这是 C 没有函数原型时的遗物。那时,(我认为)函数被假定返回int,并且它的所有参数都被假定为int。没有对函数参数进行检查。

      在当前的 C 语言中使用函数原型要好得多。
      而且你必须在 C99 中使用它们(C89 仍然接受旧语法)。

      C99 需要声明函数(可能没有原型)。如果您要从头开始编写新函数,则需要提供声明……也将其设为原型:您不会丢失任何内容并获得编译器的额外检查。

      【讨论】:

      • 不正确。在 C99 中,函数必须在调用之前显式声明。尽管如此,C99 不需要原型声明。 C99 完全支持 K&R 语法,只需进行一项更改:隐式 int 规则已被删除。
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2012-01-19
        • 1970-01-01
        • 2012-07-15
        • 1970-01-01
        • 2011-08-07
        • 2011-03-06
        相关资源
        最近更新 更多