我有一个简单的代码,我的函数在main函数之前声明:
int function1();
int function2();
int main() {
/* ... */
function1(x,y);
function2(x,y);
/* .... */
}
int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }
在我的主要功能之后,我有函数的定义:
当我在main之前声明函数时,有什么区别吗?
int function1(int x, float y);
int function2(int x, float y);
int main() {
/* ... */
function1(x,y);
function2(x,y);
/* .... */
}
int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }
答案 0 :(得分:18)
是的,他们是不同的。
在第一个例子中,您只是告诉编译器函数的名称和返回类型,而不是它预期的参数。
在第二个例子中,您在调用它们之前告诉编译器函数的完整签名,包括返回类型和预期参数。
第二种形式几乎普遍更好,因为当你调用函数时,如果你有错误的类型或数量的参数,它可以帮助你编译器更好地警告你。
另请注意,C中的int function()
是一个可以接受任何参数的函数,而不是接受 no 参数的函数。为此,您需要一个明确的void
,即int function(void)
。这主要是从C
来到C++
的人。
另见: Why does a function with no parameters (compared to the actual function definition) compile?
为了演示为什么现代C中第一个过时的表单是坏的,以下程序会在gcc -Wall -ansi -pedantic
或gcc -Wall -std=c11
的情况下编译而不会发出警告。
#include<stdio.h>
int foo();
int main(int argc, char**argv)
{
printf("%d\n", foo(100));
printf("%d\n", foo(100,"bar"));
printf("%d\n", foo(100,'a', NULL));
return 0;
}
int foo(int x, int y)
{
return 10;
}
更新: M&amp; M引起我的注意,我的示例使用int
而不是float
来执行这些功能。我认为我们都同意声明int function1()
是不好的形式,但我声明这个声明接受任何论点并不完全正确。请参阅Vlad的相关规范部分的答案,解释为何会出现这种情况。
答案 1 :(得分:14)
是的,他们有所不同;第二个是正确,第一个是整个是错误的。 GCC 5.2.1拒绝完全编译,这是错误的。它对你有用只是一个fluke:
/* this coupled with */
int function1();
int main() {
/* this */
function1(x, y);
}
/* and this one leads to undefined behaviour */
int function1(int x, float y) {
/* ... */
}
在上面的代码中,声明int function1();
没有指定参数类型(它没有原型),在C11中被认为是过时的功能(和C89,C99)就此而言)标准。如果调用了这种函数,则对参数进行默认参数提升:int
按原样传递,但float
被提升为double
。
由于您的实际函数需要(int, float)
而不是(int, double)
的参数,这将导致未定义的行为。即使您的函数预期(int, double)
但y
是整数,或者说您使用function1(0, 0);
而不是function(0, 0.0);
调用它,您的程序仍会有未定义的行为。幸运的是,GCC 5.2.1注意到function1
的声明和定义存在冲突:
% gcc test.c
test.c:9:5: error: conflicting types for ‘function1’
int function1(int x, float y) {
^
test.c:9:1: note: an argument type that has a default promotion can’t
match an empty parameter name list declaration
int function1(int x, float y) {
^
test.c:1:5: note: previous declaration of ‘function1’ was here
int function1();
^
test.c:12:5: error: conflicting types for ‘function2’
int function2(int x, float y) {
^
test.c:12:1: note: an argument type that has a default promotion can’t
match an empty parameter name list declaration
int function2(int x, float y) {
^
test.c:2:5: note: previous declaration of ‘function2’ was here
int function2();
^
并且编译器以错误代码退出,而我的tcc
快乐地编译它,没有诊断,没有。它只会产生破碎的代码。当然,如果您在头文件中有声明,并且在不同的编译单元中的定义不包含该声明,那么情况也是如此。
现在,如果编译器没有检测到这种情况,那么在运行时,可能会发生任何,如未定义的行为所预期的那样。
例如,假设参数在堆栈上传递的情况;在32位处理器int
和float
上可以容纳4个字节,而double
可以是8个字节;然后,函数调用将x
推送为int
,将y
推送为double
,即使它是float
- 总计调用者将推送12个字节并且callee只会期待8。
在另一种情况下,假设您使用2个整数调用该函数。然后调用代码将这些加载到整数寄存器中,但调用者期望浮点寄存器中的double。浮点寄存器可能包含一个陷阱值,当访问它时会终止你的程序。
最糟糕的是,您的程序可能现在表现为符合预期,因此包含heisenbug,当您使用较新版本重新编译代码时可能会导致问题编译器,或将其移植到另一个平台。
答案 2 :(得分:14)
不同之处在于,在第二个代码片段中有一个函数原型,然后编译器会检查参数的数量和类型是否与参数的数量和类型相对应。如果编译器发现不一致,编译器可以在编译时发出错误。
如果第一个代码片段中没有函数原型,则编译器会对每个参数执行默认参数提升,其中包括整数提升并将float类型的表达式转换为double类型。如果在这些操作之后,提升的参数的数量和类型与参数的数量和类型不对应,则行为未定义。编译器可能无法发出错误,因为函数定义可能位于其他编译单元中。
这里是C标准(6.5.2.2函数调用)的相关引用
2如果表示被调用函数的表达式具有类型 包括一个原型,参数的数量应该是一致的 参数数量。每个参数都应具有一个类型 value可以分配给具有非限定版本的对象 其相应参数的类型。
6如果表示被调用函数的表达式具有类型 不包括原型,执行整数促销 每个参数和类型为float的参数都被提升为 双。这些被称为默认参数促销。如果 参数个数不等于参数个数 行为未定义。如果使用类型定义函数 包括原型,并且原型以省略号结束 (,...)或促销后的参数类型不是 兼容参数的类型,行为是 未定义。如果使用不支持的类型定义函数 包括原型,以及促销后的参数类型 与促销后的参数不兼容, 除以下情况外,行为未定义:
- 一个提升类型是有符号整数类型,另一个是提升类型 是相应的无符号整数类型,值是 两种类型都可以表示;
- 两种类型都是指向合格或非限定版本的指针 字符类型或无效。
至于你的代码片段,那么如果第二个参数的类型为double
,那么代码将是格式良好的。但是,由于第二个参数的类型为float
,但相应的参数将被提升为double
类型,因此第一个代码段具有未定义的行为。
答案 3 :(得分:2)
在第一种情况下,main()
对每个参数和float
- 至 - double
促销执行整数促销。这些被称为“默认参数促销”。因此,通过传递函数期望int
和double
的{{1}}和int
,您可能最终会错误地调用函数。
有关详细信息,请参阅Default argument promotions in C function calls和答案。