为什么c++可以支持函数重载,而c语言不支持,这其实是因为c++和c在链接时对函数名修饰规则的不同。
我们知道.c文件经过编译器编译为可执行程序需要经过以下的步骤。
其中在链接过程中,当test.c中调用了在add.c中定义的Add函数时,在进行汇编后,链接前。test.obj和add.obj文件中都生成了自己的符号表,我们可以看到test.obj目标文件的符号表中没有Add的函数地址,这是因为Add是在add.c中定义的,所以Add的地址在add.obj中,那么这种情况该怎么办呢?所以链接阶段就是专门处理这种问题,链接器看到test…obj中调用Add,但是符号表中没有Add的地址,就会到add.obj的符号表中找Add的地址,然后链接到一起。
那么链接时,面对Add函数,链接器会使用哪个名字去找呢?即链接器如果要找Add函数真正的地址,会直接拿Add这个名字去每个.obj目标文件中去找该函数的地址吗?这个问题其实就是为什么c++可以函数重载而c语言不可以函数重载的原因。结论就是c语言在编译后会直接按函数名去找该函数地址,而c++会使用函数名修饰规则为每个函数重新命名。
即例如如果在c语言写如下代码,定义了两个函数名相同但形参不同的函数Add时,在汇编后形成.obj的目标文件时,符号表中直接以Add函数的名字为符号。这样在链接时,链接器会直接拿Add这个函数名去每个.obj目标文件中找Add函数地址,然后就会发现有两个Add函数的地址,这样就会不知道链接哪一个Add函数地址,所以就会出现错误。
#include
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
printf("%d\n", Add(1, 2));
printf("%d\n", Add(2.3, 3.2));
return 0;
}
现在我们在Linux系统下使用gcc编译器来编译test.c文件生成testc可执行文件。
objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,我们使用objdump命令来查看testc的反汇编目标文件。我们可以看到在反汇编目标文件中Add函数和func函数没有进行函数名修饰,而是直接显示出来函数的实际名字。
而在c++中定义了两个函数名相同但形参不同的函数Add时,在汇编后形成.obj的目标文件时,在符号表中不会直接以Add函数的名字为符号,而是会根据函数的参数等不同而对函数名加以修饰。这样在链接时,链接器会拿修饰过的函数名去每个.obj目标文件中找该函数地址,这样就避免了一个函数名有两个函数地址。所以c++中可以进行函数重载。
例如我们使用下面的代码。然后使用g++编译器将test.c按照c++的语法来进行编译和链接形成可执行程序testcpp。
然后使用objdump命令查看可执行程序testcpp的反汇编文件。可以看到在testcpp的反汇编文件中Add函数名经过g++编译器的函数名修饰规则后变为Addii,而不是原来的函数名Add了。这样链接器就不会不知道找哪一个函数的地址了。
我们写一个函数重载的c++程序通过g++编译器编译过后,观察可执行文件的反汇编文件。可以看到根据Add函数的参数不同,Add函数经过函数名修饰规则产生的函数名也不同,这就是c++可以实现函数重载的原因。
所以得出结论在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
经过上面的分析就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
如果我们写了一个c++的项目,但是使用了一个c语言写的静态库,那我们该怎样用这个c的静态库呢。例如我们之前用c语言实现的栈的操作,然后此时我们想要在c++中调用这些栈操作,此时我们就需要做一些操作,然后才能使c++中可以使用这些c语言实现的库。
我们先将使用c语言实现的栈的操作打包为静态库。
然后在Debug文件夹下可以看到打包好的静态库,即.lib文件。
此时将该.lib文件和stack.h这个头文件都拷贝到我们的c++项目中。
然后在c++项目中打开属性进行一些c++项目静态库引入的配置。
此时该c++项目中就引入了test08.lib静态库,此时需要引入该静态库的头文件。
但是当我们运行时,会发现报出了错误。这是因为上面我们所讲的,c++和c语言在链接时,c语言在函数名修饰时是直接将函数名作为标识,而c++在函数名修饰时,会加上该函数的参数,共同组成该函数的标识。所以此时当c++调用c语言写的静态库时,在链接时会发现函数名修饰不一样,即c语言是按c语言的函数名修饰规则编译的这些函数名,而c++是按c++的函数名修饰规则编译这些函数名,所以在链接时会找不到这些函数,才会报出无法解析函数的错误。
此时需要告诉c++编译器,这个stack.h头文件中实现的方法都是使用c语言实现的,当编译时要按照c语言的函数名修饰规则编译,这样链接时才可以查到到这些函数,此时程序才不报错。
这就是c++项目引用c语言实现的静态库。因为c++兼容c语法,所以静态库中的语法c++都兼容,但是c++和c语言在编译时对函数名修饰的规则不一样,所以需要做一些处理,以便c++在调用该静态库的方法时,可以找到这些方法。
c语言项目也可以调用c++写的静态库。不过也需要做一些处理。
此时先将上面的栈用c++的编译器打包成静态库,即将.c文件改为.cpp文件。
然后将打包好的静态库和头文件都拷贝到c项目的目录中。
此时运行c项目也会报错,错误原因还是因为c++和c语言在编译时函数名修饰规则不同,所以在链接时才查找不到对应函数。
此时在c++项目的.h文件中加入extern “C” {}语句,即告诉编译器在生成这些函数时,按照c的函数名修改来生成。此时会发现报错,这是因为extern “C” {}为c++语言,所以在c项目中会报错。
这时就要加上条件编译来判断了。即如果为c++文件,就加上c++的语法,如果不是c++文件,就不加上c++的语法。
虽然c项目可以使用c++写的静态库,但是因为c语言不兼容c++语法,所以该c++库中不能出现c++的一些语法,不然c语言项目识别不了这些c++语法,就会报错。