函数声明与定义C
我有一个简单的代码,其中我的函数在主函数之前声明,如下所示:函数声明与定义C,c,function,prototype,declaration,definition,C,Function,Prototype,Declaration,Definition,我有一个简单的代码,其中我的函数在主函数之前声明,如下所示: int function1(); int function2(); int main(){ /* ... */ 功能1(x,y); 功能2(x,y); /* .... */ } int函数1(int x,float y){/*…*/} int函数2(int x,float y){/*…*/} 在我的主函数之后,我有函数的定义: 当我像这样在main之前声明函数时,有什么不同吗 int function1(int x, float y
int function1();
int function2();
int main(){
/* ... */
功能1(x,y);
功能2(x,y);
/* .... */
}
int函数1(int x,float y){/*…*/}
int函数2(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) { /* ... */ }
是的,他们是不同的 在第一个示例中,您只是告诉编译器函数的名称和返回类型,而没有告诉编译器它的预期参数 在第二个示例中,在调用函数之前,您告诉编译器函数的完整签名,包括返回类型和预期参数 第二种形式是更好的,因为它可以帮助编译器在调用函数时更好地警告您参数的类型或数量错误 还要注意的是,C中的
int function()
是一个可以接受任何参数的函数,而不是一个不接受参数的函数。为此,您需要一个显式的void
,即int函数(void)
。这通常会使那些从C++
来到C
的人绊倒
另见:
为了说明为什么第一个过时的形式在现代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;
}
#包括
int foo();
int main(int argc,字符**argv)
{
printf(“%d\n”,foo(100));
printf(“%d\n”,foo(100,“bar”);
printf(“%d\n”,foo(100,'a',NULL));
返回0;
}
intfoo(intx,inty)
{
返回10;
}
更新:M&M提醒我注意,我的示例使用
int
notfloat
函数。我想我们都同意声明intfunction1()
是一种糟糕的形式,但我关于该声明接受任何参数的说法并不完全正确。请参阅Vlad的回答,了解相关规范部分,解释为什么会出现这种情况 在第一种情况下,main()
对每个参数执行整数提升,并将float
-to-double
提升。这些被称为“默认参数提升”。因此,当函数期望int
和float
时,您可能会通过传递int
和double
来错误地调用函数
有关更多详细信息,请参阅和答案。是的,它们不同;第二个是正确的,第一个是错误的。这是错误的,GCC 5.2.1拒绝完全编译它。这对你来说完全是一个问题: 在上面的代码中,声明
int function1()
没有指定参数类型(它没有原型),这在C11(以及C89、C99)标准中被认为是过时的特性。如果调用此类函数,则对参数执行默认参数升级:int
按原样传递,但float
升级为double
由于实际函数需要的参数是(int,float)
,而不是(int,double)
,这将导致未定义的行为。即使您的函数需要一个(int,double)
,但y
是一个整数,或者说您使用函数1(0,0)调用它代码>而不是函数(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();
^
编译器带着错误代码退出,而mytcc
则愉快地编译它,没有诊断,什么也没有。它只会产生坏代码。当然,如果在头文件中有声明,而在不包含该声明的不同编译单元中有定义,则情况也是如此
现在,如果编译器在运行时没有检测到这种情况,任何事情都可能发生,正如未定义行为所预期的那样
例如,假设参数在堆栈上传递;在32位处理器上,int
和float
可以容纳4个字节,而double
可以容纳8个字节;然后,函数调用会将x
按int
和y
按double
,即使是float
——调用方总共会推12个字节,被调用方只会期望8个字节
在另一种情况下,假设您使用2个整数调用函数。调用代码随后将这些数据加载到整数寄存器中,但调用方希望浮点寄存器中有一个double。浮点寄存器可能包含一个陷阱值,当被访问时,它将杀死您的程序
最糟糕的是,您的程序现在可能会像预期的那样运行,,因此在使用较新版本的编译器重新编译代码时可能会出现问题,或者将其移植到另一个平台。不同之处在于,在第二个代码段中有一个函数原型,然后编译器检查参数的数量和类型是否与参数的数量和类型相对应。如果编译器发现不一致,它可以在编译时发出错误
如果没有第一个代码段中的函数原型,那么编译器将对每个参数执行默认参数提升,包括整数提升和float类型表达式到double类型的转换。如果在这些操作之后,提升参数的数量和类型与参数的数量和类型不对应,则行为未定义。编译器可能无法发出
% 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();
^