是的,它们不同;第二个是正确的,第一个整体上是错误的。它如此错误以至于GCC 5.2.1拒绝完全编译它。它在你那里能工作只是一种偶然:
int function1();
int main() {
function1(x, y);
}
int function1(int x, float y) {
}
在上面的代码中,声明
int function1();
没有指定参数类型(它没有原型),这被认为是 C11(以及 C89、C99)标准中一种过时的特性。如果调用了这种类型的函数,则会对参数进行默认参数提升:将
float
提升为
double
,但
int
则按原样传递。
由于您实际的函数期望参数为
(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个字节;即使是
float
,函数调用也会将
x
作为
int
和
y
作为
double
推入堆栈——总共调用者会推入12个字节,而被调用者只期望8个字节。在另一种情况下,假设您使用两个整数调用该函数。调用代码将把它们加载到整数寄存器中,但调用者希望在浮点寄存器中得到一个双精度值。浮点寄存器可能包含陷阱值,当访问时会导致程序崩溃。
最糟糕的是,您的程序现在可能无法像预期的那样运行,从而包含了一个Heisenbug,这可能会在您使用更新版本的编译器重新编译代码或将其移植到另一个平台时引起问题。
(void)
;可以参考main
函数的标准签名之一:int main(void) { ... }
。 - user4520float
作为参数,所以总是导致未定义行为,因为float
不是可以通过默认参数提升得到的结果。 - Antti Haapala -- Слава Україні