2016-03-03 176 views
13

我有一个简单的代码,我的功能之前,主要的功能就像宣称:功能函数声明与定义Ç

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

您的前两行用不同于以后声明的签名(不带参数)的函数声明函数。所以前两行是不需要的。函数声明描述参数的名称,数量和类型以及返回类型。两个函数可以具有相同的名称但参数不同。它们不能仅在返回类型上有所不同。 – BryanT

+9

@BryanT这是不正确的(尽管这在C++中是正确的)。在C语言中,函数声明中的空括号表示可以使用_any_类型的_any_个参数。如果你明确想要零参数,使用'(void)';参见'main'的标准签名之一:'int main(void){...}'。 – szczurcio

+0

我同意。我在想C++。但是,将它们完全按照它们的定义进行宣布并不是更好吗? – BryanT

回答

15

是的,他们是不同的。

在第一个示例中,您只是告诉编译器关于函数的名称和返回类型,而不是其预期的参数。

在第二个示例中,在调用函数之前,要告诉编译器函数的完整签名,包括返回类型和预期参数。

第二种形式几乎普遍更好,因为它可以帮助编译器做更好的工作,当你调用函数时有错误的类型或数量的参数时会警告你。

还要注意int function()在C是可以接受任何参数,不接受没有参数的函数的函数。为此,您需要明确的void,即int function(void)。这主要是从C++趟到C

参见: Why does a function with no parameters (compared to the actual function definition) compile?

为了证明为什么第一,陈旧的形式是现代的C不好,下面的程序编译没有与gcc -Wall -ansi -pedanticgcc -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; 
} 

UPDATE:中号&中号提醒我注意,我的例子使用intfloat的职能。我认为我们都可以同意宣称int function1()是不好的形式,但是我声明这个声明接受任何论点是不正确的。有关相关规范的部分,请参阅弗拉德的解答,解释为什么是这种情况。

+0

你还应该提到OP的第一种情况是未定义的行为,即使编译器没有诊断它(它不需要这样做) –

+0

当然,第二种情况更好,因为第一种情况仍然存在未定义行为,其中意味着在我的编译器中,它无法使用默认设置进行编译。 –

2

在第一种情况下,main()执行对每个参数整数促销和float - 到 - double促销。这些被称为“默认参数促销”。所以你最终可能会错误地调用函数,通过传递intdouble函数期望intfloat

请参阅Default argument promotions in C function calls和答案的更多细节。

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位处理器上intfloat可能适合4个字节,而double可能是8个字节;然后函数调用将推动xintydouble即使是float - 总来电会推12个字节和被叫只能期待8

在另一种情况下,假设你会打电话来的函数与2个整数。调用代码然后将这些加载到整数寄存器中,但调用者期望在浮点寄存器中加倍。浮点寄存器可以包含一个陷阱值,当它被访问时会终止你的程序。

什么是最坏的情况,你的程序可能现在表现预期,从而包含heisenbug当你重新编译编译器的端口是一个较新的版本,或到另一个平台的代码,可能会出现问题。

+0

除非没有编译器会向后倾斜以告知它,否则将警告你“错误”的形式。请参阅我的答案中的示例程序。 –

+0

@Antti Haapala第一个代码片段没有错。 –

+3

@VladfromMoscow它有未定义的行为,所以通过我的书它是该死的错 –

14

不同之处在于,在第二个代码片段中存在函数原型,然后编译器检查参数的数量和类型与参数的数量和类型相对应。如果编译器发现不一致,编译器可以在编译时发出错误。

如果没有函数原型如在第一代码段,则编译器执行对包括所述整数促销和浮点类型的表达式转化为双键入每个参数默认参数提升。如果在这些操作之后,升级参数的数量和类型与参数的数量和类型不一致,则行为未定义。编译器可能无法发出错误,因为函数定义可能位于其他编译单元中。

这里是从C标准相关引号(6.5.2.2函数调用)

2如果将表示所调用的函数的表达具有类型 包括一个原型,参数的数目应同意参数数量为 。每个参数的类型应该使其 值可以分配给具有其相应参数的 类型的非限定版本的对象。

6如果将表示所调用的函数的表达具有类型 不包括原型,整数提升上 每个参数执行,且具有float类型参数被晋升为 两倍。这些被称为默认参数促销。如果参数个数不等于参数个数,则 行为未定义。如果函数的定义类型为 包含原型,并且原型以省略号 (,...)结尾,或者升级后的参数类型不是 与参数类型兼容,则行为未定义为 。如果函数定义的类型不包含 包含原型,并且升级后参数的类型与促销后参数的类型不兼容,则 行为不确定,但以下情况除外:

- 一种提升类型是有符号整数类型,另一种提升类型 是对应的无符号整数类型,值为 可在两种类型中表示;

- 这两种类型都是指向 字符类型或void的合格或不合格版本的指针。

至于你的代码片段,那么如果第二个参数的类型为double那么代码将是格式良好的。但是,由于第二个参数的类型为float,但相应的参数将被提升为double,那么第一个代码片段具有未定义的行为。