【发布时间】:2011-02-04 05:34:48
【问题描述】:
我对 C 有点陌生(我以前有 Java、C# 和一些 C++ 经验)。在 C 语言中,是否需要声明函数原型,或者代码可以在没有它的情况下编译吗?这样做是一种好的编程习惯吗?还是仅取决于编译器? (我在 Code::Blocks IDE 下运行 Ubuntu 9.10 并使用 GNU C 编译器或 gcc)
【问题讨论】:
标签: c function function-prototypes
我对 C 有点陌生(我以前有 Java、C# 和一些 C++ 经验)。在 C 语言中,是否需要声明函数原型,或者代码可以在没有它的情况下编译吗?这样做是一种好的编程习惯吗?还是仅取决于编译器? (我在 Code::Blocks IDE 下运行 Ubuntu 9.10 并使用 GNU C 编译器或 gcc)
【问题讨论】:
标签: c function function-prototypes
从不需要为 C 中的函数声明 原型,无论是在“旧”C(包括 C89/90)还是在新 C(C99)中。但是,在函数声明方面,C89/90 和 C99 之间存在显着差异。
在 C89/90 中,根本不需要声明函数。如果函数未在调用时声明,编译器会从调用中传递的参数类型隐式“猜测”(推断)声明,并假定返回类型为int。
例如
int main() {
int i = foo(5);
/* No declaration for `foo`, no prototype for `foo`.
Will work in C89/90. Assumes `int foo(int)` */
return 0;
}
int foo(int i) {
return i;
}
在 C99 中,您调用的每个函数都必须在调用点之前声明。但是,仍然没有必要专门用 prototype 声明它。非原型声明也可以。这意味着在 C99 中,“隐式 int”规则不再有效(在这种情况下,对于推断的函数返回类型),但如果函数声明时没有原型,参数类型仍然可以从参数类型中猜出。
前面的例子不能在 C99 中编译,因为 foo 没有在调用时声明。但是,您可以添加非原型声明
int foo(); /* Declares `foo`, but still no prototype */
int main() {
int i = foo(5);
/* No prototype for `foo`, although return type is known.
Will work in C99. Assumes `int foo(int)` */
return 0;
}
...
最终得到有效的 C99 代码。
尽管如此,在调用函数之前声明一个原型总是一个好习惯。
补充说明:我在上面说过,从来不需要声明函数原型。事实上,对于某些功能,这是一个要求。为了在 C 中正确调用 variadic 函数(例如printf),必须在调用点之前使用原型声明该函数。否则,行为未定义。这适用于 C89/90 和 C99。
【讨论】:
-std=c99 发出叮当声。 ideone also compiles it
-pedantic(或 -pedantic-errors,如果您希望它们是错误而不是警告)(...)”。
在 ANSI C(意为 C89 或 C90)中,您不必声明函数原型;但是,使用它们是最佳实践。该标准允许您不使用它们的唯一原因是为了向后兼容非常旧的代码。
如果你没有原型,而你调用了一个函数,编译器会从你传递给函数的参数中推断出一个原型。如果稍后在同一个编译单元中声明该函数,如果函数的签名与编译器猜测的不同,则会出现编译错误。
更糟糕的是,如果函数在另一个编译单元中,则无法获得编译错误,因为没有原型就无法检查。在这种情况下,如果编译器出错,如果函数调用在堆栈上推送的类型与函数预期的不同,您可能会得到未定义的行为。
惯例是始终在与包含函数的源文件同名的头文件中声明原型。
在 C99 或 C11 中,标准 C 要求在调用任何函数之前在范围内声明函数。许多编译器在实践中不会强制执行此限制,除非您强制他们这样做。
【讨论】:
-Werror,也许-pedantic 或-Wold-style-definition 或-Wold-style-declaration 或-Wmissing-prototypes 或-Wstrict-prototypes 或全部(或大多数) 上述的)。我通常使用-std=c11 和除-pedantic 之外的所有其他人,但我确保POSIX 或相关的#define 处于活动状态。我不经常写#define _GNU_SOURCE 或#define _BSD_SOURCE。
如果函数在使用之前定义,则不是必须的。
【讨论】:
这不是必需的,但不使用原型是不好的做法。
使用原型,编译器可以验证您是否正确调用了函数(使用正确数量和类型的参数)。
没有原型,也有可能:
// file1.c
void doit(double d)
{
....
}
int sum(int a, int b, int c)
{
return a + b + c;
}
还有这个:
// file2.c
// In C, this is just a declaration and not a prototype
void doit();
int sum();
int main(int argc, char *argv[])
{
char idea[] = "use prototypes!";
// without the prototype, the compiler will pass a char *
// to a function that expects a double
doit(idea);
// and here without a prototype the compiler allows you to
// call a function that is expecting three argument with just
// one argument (in the calling function, args b and c will be
// random junk)
return sum(argc);
}
【讨论】:
// file1.c 和 // file2.c 意味着这些是不同的文件。编译file2.c时,编译器将看不到file1.c中的定义
在C语言中,如果我们不声明函数原型并使用函数定义,则没有问题,如果函数的返回类型为“整数”,程序将编译并生成输出。在所有其他情况下,都会出现编译器错误。原因是,如果我们调用一个函数但没有声明函数原型,那么编译器会生成一个返回整数的原型,然后它会搜索类似的函数定义。如果函数原型匹配,则编译成功。如果返回类型不是整数,则函数原型不匹配并生成错误。所以最好在头文件中声明函数原型。
【讨论】:
C 允许调用之前没有声明过的函数,但我强烈建议您在使用它们之前为所有函数声明一个原型,以便编译器可以在您使用错误的参数时拯救您。
【讨论】:
您应该将函数声明放在头文件 (X.h) 中,将定义放在源文件 (X.c) 中。然后其他文件可以#include "X.h" 并调用该函数。
【讨论】:
根据C99 标准,函数原型不是强制性的。
【讨论】:
声明一个函数来编译调用代码并不是绝对必要的。不过有一些警告。假定未声明的函数返回int,编译器将首先发出有关未声明函数的警告,然后发出有关返回类型和参数类型不匹配的警告。
话虽如此,使用原型正确声明函数显然是一种更好的做法。
【讨论】:
选择“选项”菜单,然后选择“编译器 | C++ 选项'。在弹出的对话框中,选择‘CPP always’ 在“使用 C++ 编译器”选项中。 再次选择“选项”菜单,然后选择“环境 | 编辑'。确保默认扩展名是“C”而不是 'CPP'。
【讨论】: