更多术语(C,不是 C++):函数的原型声明其参数的类型。否则该函数没有原型。
void f(); // Declaration, but not a prototype
void f(void); // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype
不是原型的声明是 ANSI C 之前的保留,从 K&R C 时代开始。使用旧式声明的唯一原因是保持与旧代码的二进制兼容性。例如,在GTK 2 中有一个没有原型的函数声明——它是偶然出现的,但是如果不破坏二进制文件就不能删除它。C99 标准评论:
6.11.6 函数声明器
使用带空括号的函数声明符(不是原型格式参数类型声明符)是一个过时的特性。
建议:我建议在 GCC/Clang 中编译所有 C 代码-Wstrict-prototypes
,-Wmissing-prototypes
除了通常的-Wall -Wextra
.
发生什么了
void f(); // declaration
void f(int a, int b, float c) { } // ERROR
声明与函数体不一致!这实际上是一个编译时错误,这是因为在没有原型的函数中不能有float
参数。您不能float
在非原型函数中使用 a 的原因是,当您调用此类函数时,所有参数都会使用某些默认提升来提升。这是一个固定的例子:
void f();
void g()
{
char a;
int b;
float c;
f(a, b, c);
}
在这个程序中,a
被提升为int
1并c
被提升为double
. 所以 for 的定义f()
必须是:
void f(int a, int b, double c)
{
...
}
见 C99 6.7.6 第 15 段,
如果一种类型具有参数类型列表,而另一种类型由不属于函数定义的函数声明符指定且包含空标识符列表,则参数列表不应有省略号终止符,并且每个参数的类型应与应用默认参数提升所产生的类型兼容。
答案 1
当我们f
使用正确的参数、错误的参数和根本没有参数调用时,在案例 1 和 2 中编译时会发生什么?运行时会发生什么?
当您调用f()
时,参数会使用默认促销来提升。如果提升的类型与 的实际参数类型匹配f()
,那么一切都很好。如果它们不匹配,它可能会编译,但你肯定会得到未定义的行为。
“未定义的行为”是规范的说法,即“我们不保证会发生什么”。也许你的程序会崩溃,也许它会正常工作,也许它会邀请你的姻亲来吃饭。
有两种方法可以在编译时获得诊断。如果您有一个具有跨模块静态分析功能的复杂编译器,那么您可能会收到一条错误消息。您还可以使用 GCC 获取非原型函数声明的消息,使用-Wstrict-prototypes
-- 我建议在您的所有项目中打开它(使用 GTK 2 的文件除外)。
答案 2
如果我f
用参数声明,但没有参数定义它,会有所不同吗?我应该能够解决函数体中的参数吗?
它不应该编译。
例外
实际上有两种情况允许函数参数与函数定义不一致。
可以传递char *
给期望的函数,void *
反之亦然。
可以将有符号整数类型传递给期望该类型的无符号版本的函数,反之亦然,只要该值在两种类型中都是可表示的(即,它不是负数,并且不超出签名类型)。
脚注
1:有可能char
提升到,unsigned int
但这很不常见。