谈谈C语言中函数声明/定义冲突时的处理(VS2010、VC6)

时间:2022-08-24 22:42:32

首先,本文中讨论的例子采用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 list
xx.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的一些天然能力不足~~~。


至于具体标准里如何规定的,后续有时间打算再研究一下标准。。。。