【问题标题】:How is the ANSI C function declaration an improvement on the old Kernigan and Ritchie style?ANSI C 函数声明如何改进旧的 Kernigan 和 Ritchie 风格?
【发布时间】:2026-01-03 23:15:01
【问题描述】:

关于 ANSI C 函数声明,这对旧的 K&R 风格有何改进?我知道它们之间的区别,我只是想知道使用旧样式会出现什么问题,以及新样式是如何改进的。

【问题讨论】:

  • 我看到有人投票结束这个。我同意这个问题可能含糊不清,但我相信有一个具体的答案(见我的)。

标签: c


【解决方案1】:

旧式函数声明,特别是不允许在编译时检查调用。

例如:

int func(x, y)
char *x;
double y;
{
     /* ... */
}

...

func(10, 20);

编译器看到调用时不知道函数func的参数类型,所以无法诊断错误。

相比之下:

int better_func(char *x, double y) {
    /* ... */
}

...

better_func(10, 20);

将导致编译器错误消息(或至少是警告)。

另一个改进:原型使函数可以具有float 类型的参数,以及比int 更窄的整数类型(3 个char 类型和两个short 类型)。如果没有原型,float 将提升为 double,而窄整数类型将提升为 intunsigned int。对于原型,float 参数作为 float 传递(除非函数是可变参数,如 printf,在这种情况下旧规则适用于可变参数)。

C Rationale 文档在第 6.7.5.3 节中讨论了这一点,可能比我有的更好:

函数原型机制是最有用的补充之一 到C语言。当然,该功能在许多方面都有先例 过去 25 年源自 Algol 的语言。具体形式 标准中采用的很大程度上基于 C++。

函数原型提供了强大的翻译时错误 检测能力。在没有原型的传统 C 实践中,它 译者很难发现错误(错误 参数的数量或类型)在对另一个声明的函数的调用中 源文件。在运行时检测到此类错误 或通过使用辅助软件工具。

在函数原型范围之外的函数调用中,整数 参数应用了 integer Promotionfloat 参数扩展为 double。在这样的电话中是不可能的 传递未转换的 charfloat 参数。功能 原型让程序员明确控制函数 参数类型转换,因此经常不适当和 有时,参数的低效默认扩展规则可能是 被实施压制。

还有更多;去读吧。

【讨论】:

  • 有趣的是,K&R 风格更符合 C 声明的基本思想; “声明模仿使用”。
  • 为什么编译器检测不到K&R风格函数的类型不匹配?所有类型信息仍然存在。
  • @templatetypedef:不一定。旧式的非原型函数 declaration 不包括函数体,因此它不提供参数类型信息。示例:int func(); 中的 some_header.h。完整的 定义 确实提供了该信息,但是 (a) 它可能不在同一个源文件中,并且 (b) 编译器不需要使用它,即使它是。使用无效参数调用非原型函数具有未定义的行为;它不需要被诊断出来。
  • 非常感谢,我不知道他们怎么会认为我的问题含糊不清,因为您已经完美地回答了我,再次感谢 :)
  • @RichardGray:“为什么 X 比 Y 更好”形式的问题通常可以“引发辩论、争论、投票或扩展讨论”。我认为你的问题是一个例外。
【解决方案2】:

K&R 中的一个非定义函数声明如下所示

int foo();

并引入了一个接受未指定个参数的函数。这种声明风格的问题很明显:它既没有指定参数的数量,也没有指定它们的类型。编译器无法根据调用点的参数数量或其类型检查调用的正确性。在参数类型与预期参数类型不匹配的情况下,编译器无法执行参数类型转换或问题和错误消息。

函数声明,在K&R中用作函数定义的一部分,如下所示

int foo(a, b) 
int a;
char b;
{ ...

它指定了参数的数量,但仍然没有指定它们的类型。此外,即使这个声明似乎暴露了参数的数量,它仍然像int foo();一样正式声明foo,这意味着将其称为foo(1, 2, 3, 4, 5)仍然不构成违反约束。

新风格,即使用原型声明更好,原因很明显:它公开了参数的数量和类型。它强制编译器检查调用的有效性(关于参数的数量和类型)。并且它允许编译器执行从参数类型到参数类型的隐式类型转换。

原型声明还提供了其他不太明显的好处。由于调用者和函数本身都知道函数参数的数量和类型,因此可以在调用时选择最有效的方法来传递参数(调用约定),而无需查看函数定义。如果没有这些信息,K&R 实现就不得不对所有函数遵循一个预先确定的“一刀切”的调用约定。

【讨论】: