目录
C++支持函数重载的原理 :名字修饰(name Mangling)
C++支持函数重载的原理 :名字修饰(name Mangling)
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
- 实际项目通常是由多个头文件和多个源文件构成。
【假如a.cpp中调用了b.cpp中定义的Add函数时】
执行编译后,在链接前,a.o 的目标文件中没有 Add 的函数地址
因为 Add 是在 b.cpp 中定义的,所以 Add 的地址在 b.o 中。
- 链接阶段就是专门处理这种问题
链接器看到 a.o 调用 Add,但是没有 Add 的地址,就会到 b.o 的符号表中找 Add 的地址,然后链接到一起。面对 Add 函数,链接接器会使用哪个名字去找呢?每个编译器都有自己的函数名修饰规则,这里采用的是Linux下的修饰规则。
- 在Linux下 g++ 和 gcc 的修饰规则
现在有一个 .c 文件:test.c
// test.c
void func(int a, double b, int* p)
{}
int main()
{
func(1, 2, 0);
return 0;
}
采用C语言编译器(gcc)结果
#执行编译
gcc -o test test.c
#查看生成的二进制文件 test
objdump -S test
#执行后能看到 func 函数的地址及信息,例如:
00000000004004ed <func>:
...
...
...
采用C++编译器(g++)结果
#执行编译
g++ -o test test.c
#查看生成的二进制文件 test
objdump -S test
#执行后能看到 func 函数的地址及信息,例如:
00000000004005c1 <Z4funcidPi>:
...
...
...
可以看出
gcc的函数修饰后名字不变。
而g++的函数修饰后变成【_Z+函数长度+函数名+类 型首字母】。
为什么C++支持函数重载,而C语言不支持函数重载呢?
通过这里就应该能理解C++支持函数重载,而C语言不支持函数重载。
- C语言同名函数没办法区分。
- C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
- 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,而且编译器也无法通过,因为调用时编译器没办法区分。
extern 关键字
由于C和C++编译器对函数名字修饰规则的不同,在有些场景下可能就会出问题,比如:
- C++中调用C语言实现的静态库或者动态库,反之亦然
- 多人协同开发时,有些人擅长用C语言,有些人擅长用C++
在这种 混合模式 下开发,由于 C 和 C++ 编译器对函数名字修饰规则不同,可能就会导致链接失败,在该种场景下,就需要使用 extern "C" 。在函数前加 extern "C" ,意思是告诉编译器,将该函数按照C语言规则来编译。
// 创建一个C语言的静态库
//calc.h///
#pragma once
/*
* 注意:
* 在实现该库时,并不知道将来使用该静态库的工程是C语言工程还是C++工程
* 为了能在C/C++工程中都能使用,函数声明时需加上extern "C"
*
* __cplusplus:是C++编译器中定义的宏,即用该宏来检测是C工程还是C++工程
*
* #ifdef __cplusplus
extern "C"
{
#endif
// 要导出函数的声明
#ifdef __cplusplus
}
#endif
作用:如果是C++工程,编译器已经定义_cplusplus宏,编译时该宏是可以被识别的,被声明的函数就被
extern "C"修饰了,
此时C++编译就知道,静态库中的函数是按照C的方式编译的,这样在链接时就会按照C的方式找函数名字
如果是C工程,编译器未定义_cplusplus宏,编译时该宏无法被是被,则条件编译就无效,函数就不会被
extern "C"修饰
*/
#ifdef __cplusplus
extern "C"
{
#endif
int Add(int left, int right);
int Sub(int left, int right);
#ifdef __cplusplus
}
#endif
//calc.c///
#include "calc.h"
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
【经典面试题】
Q: 下面两个函数能形成函数重载吗?有问题吗或者什么情况下会出问题?
void Func(int a = 10)
{
cout<<"void Func(int a = 10)"<<endl;
}
void Func(int a)
{
cout<<"void Func(int a)"<<endl;
}
A: 这两个函数不能实现重载,编译器会报错。
- 函数重载要求函数的参数列表必须不同。
- 然而,在代码中,这两个函数的参数列表是相同的(都只有一个
int
类型的参数)。 - 虽然第一个函数提供了一个默认参数值
10
,但默认参数不被编译器用来区分重载函数。 - 因此,编译器会认为这两个函数是同一个函数,导致重定义错误。