【问题标题】:What's the point of function prototyping?功能原型设计的意义何在?
【发布时间】:2011-05-02 14:35:02
【问题描述】:

我正在按照指南学习 curses,以及 main() 之前的原型函数中的所有 C 代码,然后定义它们。在我的 C++ 学习中,我听说过函数原型,但从未做过,据我所知,它对代码的编译方式没有太大影响。这是程序员的个人选择吗?如果是这样,为什么它完全包含在 C 中?

【问题讨论】:

  • 两个函数相互调用时需要前向声明。
  • 我不明白为什么。我不是在问它们是否有必要,我只是不明白为什么它们首先在标准中。
  • 简短的回答是:编写编译器已经够难了,要求事先声明所有内容是保持编译器一次通过的有效方法。
  • 为什么人们将其作为完全重复的副本关闭?另一个问题是询问是否需要。这个人在问为什么一个人会使用它。仅仅因为他们在标题中都有原型这个词并不意味着他们是同一个问题。

标签: c function-prototypes


【解决方案1】:

函数原型最初并未包含在 C 中。当您调用一个函数时,编译器只是相信它会存在并接受您提供的参数类型。如果您的参数顺序、编号或类型错误,太糟糕了——您的代码会在运行时以神秘的方式失败。

后来的 C 版本增加了函数原型来解决这些问题。在某些情况下,您的参数会隐式转换为声明的类型或标记为与原型不兼容,并且编译器可能会将错误的顺序和类型数量标记为错误。这具有启用可变参数函数和它们所需的特殊参数处理的副作用。

请注意,在 C 中(与在 C++ 中不同),声明为 foo_t func() 的函数与声明为 foo_t func(void) 的函数相同。后者的原型是没有参数的。前者声明了一个没有原型的函数。

【讨论】:

  • +1 最后一节说明声明和原型之间的区别。
【解决方案2】:

函数原型是过去编译器编写的遗留物。过去,编译器不得不多次遍历源文件来编译它被认为是非常低效的。

在 C 语言中,在某些上下文中,以一种方式引用函数在语法上等同于引用变量:考虑使用指向函数的指针与使用指向变量的指针。在编译器的中间表示中,两者在语义上是不同的,但在语法上,无法从上下文中判断标识符是变量、函数名还是无效标识符。

由于无法从上下文确定,没有函数原型,编译器每次编译时都需要对每个源文件进行额外的传递。这将为任何编译添加额外的 O(n) 因子(也就是说,如果编译是 O(m),那么现在将是 O(m*n)),其中 n 是项目中的文件数。在大型项目中,编译已经在几个小时内完成,因此非常不希望使用两次编译器。

转发声明所有函数将允许编译器在扫描文件时构建函数表,并能够确定何时遇到标识符是指函数还是变量。

因此,C(以及扩展为 C++)编译器的编译效率非常高。

【讨论】:

  • 这不一定是真的——如果他们不需要函数原型,他们就不必重新解析相同的#include 三十次。这就是为什么预编译头文件在减少 MSVC 中的构建时间方面如此有效的原因——因为#include 需要很长时间。
  • 我喜欢开头。是的,编译模型是罪魁祸首。但我不太喜欢捍卫这种模式。建议 C++ 编译器在编译时可以非常高效,最终是一场灾难。加油!
【解决方案3】:

我的印象是,客户可以访问库的 .h 文件并查看他们可以使用哪些功能,而无需查看实现(可能在另一个文件中)。

有助于查看函数返回什么/什么参数。

【讨论】:

  • 从头文件中获取可用函数是个坏主意,因为头文件可能包含应用程序程序员无法使用的任意魔法。认真的开发人员会从随库提供的文档中获得他需要的所有信息。对于标准 C、POSIX 和更多(甚至是 Windows API),文档是免费提供的,甚至可能已经存在(手册页、Windows 帮助……)如果您需要从标题中收集原型,您就在错误的轨道。
【解决方案4】:

需要在 C 中进行原型设计,以便您的程序知道您有一个名为x() 的函数,而您还没有定义它,这样y() 就知道存在并且存在一个x()。 C 确实自顶向下编译,因此需要事先定义它是简短的答案。

x();
y();
main(){

}

y(){
x();
}

x(){
...
more code ...
maybe even y();
}

【讨论】:

  • 我认为这个答案完全没有抓住重点。一方面,您也可以在没有原型的范围内和定义之前调用任何函数(某些规则适用)。其次,“自上而下的编译”(无论是什么意思)不是 C 的一部分,也不需要以某种特定的方式编译 C。
  • 第三,您的代码被截断甚至不包含适当的原型!太草率了,gcc -Wall -pedantic -std=c89 x.c 产生了 10 个警告。
【解决方案5】:

它允许您有这样一种情况,即您可以在包含父容器类的单独 .h 文件中定义一个迭代器类。由于您已在迭代器中包含父标头,因此您不能使用诸如“getIterator()”之类的方法,因为返回类型必须是迭代器类,因此需要您在迭代器中包含迭代器标头父标头创建包含的循环循环(一个包含另一个包含自身的另一个,它再次包含另一个,等等)。

如果将迭代器类原型放在父容器内,就可以有这样的方法,不包括迭代器头。它之所以有效,是因为您只是在说这样的对象存在并且将被定义。

有一些方法可以绕过它,比如使用预编译的头文件,但在我看来,它不太优雅,并且有很多缺点。当然,这是 C++,而不是 C。但是,在实践中,您可能希望以这种方式排列代码,类除外。

【讨论】:

  • C 中的迭代器类?这个问题提到了 C++,但是是关于 C 的基本原理,所以我认为需要一个不同的例子:-)
猜你喜欢
  • 2012-12-02
  • 2010-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多