C++函数重载原理及extern“C“原理

前言

在这里插入图片描述

最近偶然看到2个面试题,挺有趣的,如下👇:

  1. C语言中为什么不能支持函数重载?C++中函数重载底层是怎么处理的?
  2. extern ”C" 是干什么的?原理是什么。

当时博主一看到这2个问题,似乎看过一点相关内容,但又不太能完整回答上来,于是便有了此篇博客。

1.函数重载原理

要先弄明白函数重载原理,我们先来回顾编译链接的过程:

编译链接过程

假设我们写了3个源文件,如下。
f.h f.cpp main.cpp

// main.cpp
int main()
{
	f(1, 2.222);
	f(2.222, 1);
	return 0;
}

// f.h
void f(int a, double b);
void f(double a, int b);

// f.cpp
void f(int a, double b)
{
	printf("int,double\n");
}

void f(double a, int b)
{
	printf("double,int\n");
}
  1. 预处理 — 头文件展开 + 宏替换 + 去掉注释 + 条件编译
    f.i main.i
  2. 编译 — 检查语法,生成汇编代码
    f.s main.s
  3. 汇编 — 把汇编代码转成二进制机器码,形成符号表。(符号表里生成有函数名和函数地址映射)
    f.o main.o
    f.o里面有f函数的符号表,main.o 里面有main函数的符号表。
  4. 链接 — 链接对应的库,找调用函数的地址,链接到一起生成可执行程序。
    a.out

每个目标文件会生成一个符号表 链接时就能找到函数地址,这也正是符号表的意义之一。
在这里插入图片描述

关于函数调用

函数调用就是去call 函数的地址,但在 main.cpp 里面找不到 add 函数的地址,因为只包含了 f.h 文件 里面只有函数的声明,声明没有函数的地址。
有了声明,编译阶段就能通过了,编译器会认为函数定义在其他地方,后续链接时再去找函数的定义。
在这里插入图片描述

链接时如果找不到就会报错
在这里插入图片描述

C++ 通过函数名修饰规则支持了函数重载。
在这里插入图片描述
3 表示函数名的长度 add就是函数名 ii 表示2个int dd表示2个double
通过函数名修饰规则就能通过 call 调用相应函数地址。

而C语言直接拿函数名作为地址。
在这里插入图片描述

总结

  1. 归根到底,还是因为C编译器和C++编译器对函数名的修饰不同。在g++下的修饰规则是:(Linux系统下修饰规则)
    [ _Z+函数长度+函数名+类型首字母 ]
    注意:Linux编译器不区分文件名后缀,因此g++编译器可以编译cpp文件,gcc编译器也可以编译cpp文件。
    g++ -o tcpp f.cpp test.cpp gcc -o tc f.cpp test.cpp
  2. 这其实也告诉我们为什么函数的返回类型不同,不会构成函数重载,因为修饰规则并不会受返回值的影响。

2.extern “C” 作用

由前面的函数重载原理剖析我们已经知道,链接时会去找相应符号表里找调用函数的地址,除此之外,链接时还会链接静态库或动态库。
C++既然兼容C的库,为什么不能直接调用C的库呢?
还是函数名修饰规则的原因,C++项目链接时去找相关函数的地址,按照C++的函数名修饰规则去找,找的是 [ _Z+函数长度+函数名+类型首字母 ] ,而 C库里面提供的就只是 函数的名称作为地址,因此是找不到的。

那C++该如何调用C库呢?

举个例子👇

C++调C库

先建立一个Stack_c静态库:
在这里插入图片描述
然后选择生成解决方案,可以在相应文件夹里看到.lib文件了。
在这里插入图片描述现在有一个C++的项目,想用那边的静态库,怎么用呢?
当然是包头文件啦,但是C++项目里面没有Stack相关头文件,因此需要用相对路径包含。
在这里插入图片描述
通过相对路径引入头文件后,在主函数内调用isValid,编译可以通过,但是链接不上。
因此还需要配置一下附加库项目和依赖项。
在这里插入图片描述
在这里插入图片描述
此时再点击生成解决方案,发现还是链接不上,为什么呢?不要着急。
因为现在就相当于是C++的项目去调用C的静态库,正常情况自然不能链接成功啦。
当然,我们只要将Stack.c 改成Stack.cpp 那么就能链接成功了。
C++项目调用C++库自然是可以的,就像C项目也能正常调用C库。
在这里插入图片描述
我们一开始 include 了Stack.h 也就是项目中有了Stack相关函数的声明,通过Stack_c 生成的静态库 .lib 文件里面有符号表,符号表里面有Stack相关函数的地址,进而就能找到Stack相关函数的定义了。
如果非想用C++ 调用C库呢?
这时候 extern “C” 就起作用啦!

extern "C"概念

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译
这样C程序可以调用,C++程序也可以调用(C++兼容C)
回到上面举的例子:
在这里插入图片描述

再举个工程中实际的的例子:👇

tcmalloc 是 google 用 C++ 实现的一个项目,他提供 tcmallc() 和 tcfree 两个接口来使用,但如果是C项目就没办法使用,那么他就使用 extern “C” 来解决。
extern "C" void* tcmalloc(unsigned int n); 注意用了 extern "C" 就没法用重载了

谷歌的一个开源项目:
在这里插入图片描述

C调用C++库

那问题来了,C如何调用C++的库呢?
难道用 extern “C++” 吗? 注意没有 extern “C++” , 只有 extern “C”

先建立 Stack_CPP 静态库,同上。
依旧是把项目的配置类型改为静态库,然后生成解决方案,在相应Debug文件中就能看到 Stack_CPP.lib 文件
在这里插入图片描述
然后新建一个C的项目,注意,C里面是没有 extern “C” 的。
还是通过相对路径include C库里面的Stack.h 文件,然后再配置连接器里的附加库目录,和附加依赖项。
在这里插入图片描述
再次生成解决方案,同样是无法成功链接的,毕竟现在是C项目调用C++静态库。
C的项目链接时找函数的地址,是通过函数的名字作为地址去C++库里面找的,但C++库里函数名字修饰规则不一样,自然是找不到的。
C里面可没有 extern “C++” ,因此只能在C++里面动手了。
在函数声明前加上 extern “C”,告诉编译器,按C的规则去修饰这些函数,C++的静态库可以通过了。
在这里插入图片描述
但此时C项目中调用又通不过了。
在这里插入图片描述

在这里插入图片描述C中头文件展开出问题了!头文件展开时是直接把整个 .h 文件中的内容复制过来的,但是不可避免的就会在C的项目中出现了,extern “C”,自然通不过了。
此时我们可以借鉴Google那个开源项目的处理技巧,利用条件编译来处理。
在这里插入图片描述
此时C项目就能调用C++的库了。

另一种写法:

在这里插入图片描述
再问一个问题,此时能否实现函数重载呢?
当然不能啦!用C的函数名修饰规则找函数地址,如果还有函数重载的话,找到的都是一样的名字,无法区分。

3.源码链接

总结

  1. C++调用C库

    // 告诉编译器,extern "C" 声明的函数是C库,要用C的方式去调用。
    extern "C"
    {
    	#include "../5-4Stack_c/Stack.h"
    }
    
  2. C调用C++库

    #ifdef __cplusplus
    	#define EXTERN_C extern "C" // 识别到c++文件就用 extern "C" 替换 EXTERN_C
    #else
    	#define EXTERN_C // 否则就用空替换 EXTERN_C
    #endif // __cplusplus
    
    // 告诉编译器,按C的规则去修饰这些函数
    EXTERN_C void StackInit(Stack * pst);
    

尾声

🌹🌹🌹

写文不易,如果有帮助烦请点个赞~ 👍👍👍

Thanks♪(・ω・)ノ🌹🌹🌹

😘😘😘

👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。
附GitHub仓库链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值