首先,本文中讨论的例子采用C语言,而非C++语言。
使用示例分析这个问题:
例子1
#include <stdio.h> void hello(int a); void hello(int a, int b); void hello(int a) { printf("hello, %d\n", a); } void main() { hello(1); }
在这个例子中,hello()函数声明了两次,定义了一次。
VS2010的编译结果为:
xx.c(4): warning C4031: second formal parameter list longer than the first listxx.c(7): warning C4029: declared formal parameter list different from definition
VC6的编译结果为:
xx.c(4) : warning C4031: second formal parameter list longer than the first list
xx.c(7) : warning C4029: declared formal parameter list different from definition
xx.c(13) : error C2198: 'hello' : too few actual parameters
例子2
#include <stdio.h>
void hello(int a);
void hello(int a, int b);
void hello(int a)
{
printf("hello, %d\n", a);
}
void hello(int a, int b);
void main()
{
hello(1);
}
例子2比例子1多了一个函数声明(红色部分)。
在这个例子中,hello()函数声明了三次,定义了一次。
VS2010的编译结果为:
xx.c(4): warning C4031: second formal parameter list longer than the first list
xxc(7): warning C4029: declared formal parameter list different from definition
xx.c(11): warning C4031: second formal parameter list longer than the first list
xx.c(15): error C2198: 'hello' : too few arguments for call
VC6的编译结果为:
xx.c.c(4) : warning C4031: second formal parameter list longer than the first list
xx.c.c(7) : warning C4029: declared formal parameter list different from definition
xx.c(15) : error C2198: 'hello' : too few actual parameters
----------------------------------------------------------------------------------------------------------------------------------------
对比这两个例子,可以做出以下分析
1)在函数调用中,如果提供的参数数量小于函数原型(函数原型的确定,后面讨论)中要求的参数数量,则会报错“too few actual parameters”,在VS2010和VC6中均如此。
2)在存在多个函数声明、存在定义的情况下,如何确定函数原型?
有了(1)的分析结果,我们可以根据函数调用来确认函数原型,当不报错时,hello(int a)就是函数原型,报错时hello(int a, int b)才是函数原型。
具体分析过程不再讨论,说一下我粗略得到的结论:
同时存在多个函数声明和定义时,
在VS2010中,函数原型由所有声明(包含定义)中最后一条确定。
在VC6中,函数原型由所有声明(不包含定义)中的最后一条确定。
3)实际2)中的分析存在一个问题,“最后一条”,怎么算是最后一条,如果在函数调用后,还有函数声明呢?函数调用后的函数声明后还有函数调用呢?如何处理?
继续以实例分析.
例子3
#include <stdio.h>
void hello(int a);
void hello(int a, int b);
void hello(int a)
{
printf("hello, %d\n", a);
}
void hello(int a, int b);
void main()
{
hello(1);
}
void hello(int a);
void callhello()
{
hello(1);
}
例子3中,在main()函数后,又增加了一个函数声明和函数调用。
VS2010编译结果如下(略去warning)
xx.c(15): error C2198: 'hello' : too few arguments for call
VC6编译结果如下(略去warning)
xx.c(15) : error C2198: 'hello' : too few actual parameters
VS2010和VC6处理一致,可以看到第一次hello()调用报错,第二次却没有报错。
这就说明,前后两次函数调用,用于检查函数调用是否正确的函数原型是不一样的:
我们很容易猜测到,用于检验函数调用的函数原型,是由源文件中、函数调用前、该函数的所有声明决定的。
可以继续验证:
例子4
#include <stdio.h>
void hello(int a);
void hello(int a, int b);
void hello(int a)
{
printf("hello, %d\n", a);
}
void hello(int a, int b);
void main()
{
hello(1);
}
void hello(int a, int b);
void callhello()
{
hello(1);
}
VS2010:
xx.c(15): error C2198: 'hello' : too few arguments for call
xx.c(22): error C2198: 'hello' : too few arguments for call
VC6:
xx.c(15) : error C2198: 'hello' : too few actual parameters
xx.c(22) : error C2198: 'hello' : too few actual parameters
修改最后第2个hello()调用前的函数声明,第2个hello()调用也报错了。
结合上面的结论,可以得出:
同时存在多个函数声明和定义时,
用于检验函数调用的函数原型,是由源文件中、函数调用前、该函数的所有声明中的最后一条决定的;
在VS2010中,所有声明包含定义,在VC6中,所有声明不包含定义。
PS: 如果一个函数调用前,只存在这个函数的定义,那函数调用的检查肯定用函数定义中的函数原型,这一点要强调一下。
但是,同时,我们知道,函数原型不仅仅适用于函数调用的编译检查,还涉及到链接的问题。
不过在C语言中,对这方面,我们不需要太多考虑。因为链接依靠的是符号表,符号表中,函数的符号是由函数原型决定的。
但是C语言中没有name mangling机制,导致函数的符号实际只由函数名决定。
同样是一个例子:
例子5
//main.c #include <stdio.h> void hello(int a); void main() { hello(1); }
//sub.c #include <stdio.h> void hello(int a, int b) { printf("hello, %d %d\n", a, b); }1)后缀名为C,使用VS2010和VC6编译均正常,无报错。
2)如果修改后缀名为C++,再编译,
VS2010报错:
main.obj : error LNK2019: unresolved external symbol "void __cdecl hello(int)" (?hello@@YAXH@Z) referenced in function _main
xxxx.exe : fatal error LNK1120: 1 unresolved externals
VC6报错:
main.obj : error LNK2001: unresolved external symbol "void __cdecl hello(int)" (?hello@@YAXH@Z)
xxxx.exe : fatal error LNK1120: 1 unresolved externals
从这一点来看,C语言对函数原型的检查机制,天然比不上C++的检查机制。
最后,再谈二个很有意思的东西。
1、旧式风格函数声明
例子6
#include <stdio.h> void hello(int a, int b) { printf("hello, %d, %d\n", a, b); } void hello(); void main() { hello(1, 2); }
这个程序会不会编译通过呢?
根据我们上面得到的结论,VC6和VS2010,在检查hello(1,2)调用时,使用的是hello(),应该报错。
但是编译一下发现,程序正常通过编译,WHY?
这里涉及到一个旧式声明的问题。
void hello();既可以看成是一个旧式声明(只给出函数的返回类型),也可以看成没有参数的函数的新风格原型。
当然旧式声明早已经是垃圾堆里的东西了,但是编译器却要保证对旧式风格的兼容,因此hello()会被理解成一个旧式风格的声明。
SO.....void hello();不会影响到函数调用的检查。
我已经尝试过,在上面所有的例子中,随意添加void hello();不会影响编译结果的。
2、过多的函数参数
例子7
#include <stdio.h> void hello(int a) { printf("hello, %d\n", a); } void main() { hello(1, 2); }
上面什么情况??过多的函数参数,看一下编译结果把:
VS2010:(warning level3)
XX.C(10): warning C4020: 'hello' : too many actual parameters
VC6:(warning level3)
XX.C(10) : warning C4020: 'hello' : too many actual parameters至少在:(warning level3)下,是仅有WARNING,没有报错的。
运行一下,也是正常的。
那修改后缀名为c++??
VS2010:
1>ClCompile:
1> main.cpp
1>f:\prj_cs\c\try_0819\try_0819\main.cpp(10): error C2660: 'hello' : function does not take 2 arguments
VC6:
d:\my documents\test\test0818\test.c(10) : warning C4020: 'hello' : too many actual parameters
Linking...
test.obj : error LNK2005: _main already defined in main.obj
main.obj : error LNK2001: unresolved external symbol "void __cdecl hello(int)" (?hello@@YAXH@Z)
Debug/test0818.exe : fatal error LNK1120: 1 unresolved externals
不太一样,一个编译就报错,一个链接才报错,但终究也是都报错了。
由此又印证了,C的一些天然能力不足~~~。
至于具体标准里如何规定的,后续有时间打算再研究一下标准。。。。