我认为最权威的程序员之一(Richard Stallman)编写的最着名的操作系统(linux)中最常用的系统函数之一(ls
)可能是一个写得很好的例子码。
因此,作为开源,我决定查看代码(参见例如here)。在那里我发现了几个在main()
之后定义的函数,因此在他们调用之后,我预计这种函数非常罕见。
有经验的C程序员会对此发表评论吗?
答案 0 :(得分:9)
Stallman在这里做的事情绝对没有错。
C语言允许之后将定义的函数的forward declaration。
这有许多优点,不应被视为不良行为,而是非常好的行为。
优点(并非详尽无遗):
- 让程序员快速了解C代码公开的API,而无需查看所有代码
- 允许使用头文件,其中声明稍后将在编译过程中定义的函数。因此,每次使用时都不必定义函数。
在这个ls
实现的情况下,他只是预先声明了他将在main()
中使用的函数,但如果仔细观察,主要函数是第一个出现的函数。
这很可能是出于可读性的考虑,因此您无需一直向下滚动即可到达程序的入口点。
请注意,词汇在这里很重要:
- 函数声明意味着:只告诉编译器,在代码的某处,将定义一个具有相同名称的函数。
- 函数定义:实际的函数实现
int my_function( char *text); // function declaration, no implementation
int main( int argc, char **argv)
{
return my_function(argv[0]); // use of the declared function
}
// actual function definition / implementation
int my_function( char *text )
{
printf("%s\n", text);
}
编辑:仔细查看代码后,您可以看到Stallman没有向前声明他的所有功能。他还有一种相当奇怪的定义函数的方式。我将这归因于代码的旧版本,该代码的日期是1985年,当时C编译器的定义不如今天。 在声明或定义之前,它必须允许这种函数用法。
最后但并非最不重要的是,最新版本的ls
源代码可在此处找到:http://coreutils.sourcearchive.com/documentation/7.4/ls_8c-source.html,
与'85(Back-to-the-Future)版本相比,它具有更多符合C99标准的编码。
答案 1 :(得分:4)
在C中,您通常在头文件中定义function prototypes,然后包含头文件,因此可以在调用函数后安全地定义函数。
在您提供的示例文件中,程序足够小,原型只是在程序声明之前放在文件的顶部,但同样的原则适用。
编辑:此外,该文件是pre-K& R C,这有点酷,但与现代C有一些显着差异,你不一定要模仿它。
答案 2 :(得分:2)
代码将在概念上阅读,就像一个很好的规范
main
之后的定义)。他忽略了在main
之前放置的函数的声明(以满足第一个子弹 - 定义接口)被许多程序员认为是关于现代C的坏风格。
然而,代码使用旧式函数定义,其声明不会为调用者定义参数类型。据推测,将此代码更新为现代C会破坏许多仍使用ANSI前编译器的非常旧的系统。
答案 3 :(得分:2)
对我而言,这是一个来自原型前时代的遗物,看看更新的代码,你常常会看到一个倒置的代码顺序。我的主要缺点是你必须在顶部声明函数,然后函数本身向下。如果更改功能的签名,则还必须在2个位置进行更改。请注意,静态函数的情况也是如此,这部分代码中根本没有使用的功能...... 如果你不这样做,肯定会编译,但你会被C的隐含声明早先或稍后咬过。
说真的,找到一些比一些oldskool K& R样式C更新的代码,从那时起C语言发展很快,你可能会在这个过程中找到一些坏习惯。
答案 4 :(得分:1)
在调用函数之前,您需要在范围内拥有原型。函数定义用作原型
/* this is a definition and a prototype */
int fx1(void) {
return 42;
}
/* this is only a prototype */
int fx2(int, const char*);
int main(void) {
fx1(); /* ok, prototype in scope */
fx2(42, "foobar"); /* ok, prototype in scope */
}
/* fx2 definition.
** it is undefined behaviour if the prototype here
** does not match the previous prototype */
int fx2(int k, const char *t) {
return strlen(t) - k;
}
通常,原型在头文件中声明,头文件包含在实现文件的顶部,在任何代码之前。
#include "prototypes.h"
/* define functions in any order: prototypes are all in scope */