extern C的前世今生

extern C的前世今生


extern "C"的主要作用就是为了==能够正确实现C++代码调用其他C语言代码。==加上 extern "C"后,会指示编译器这部分代码按 C语言的进行编译,而不是 C++的。

由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

其实有extern C就是为了让C++能够使用C的库文件

extern "C"的前世今生

C++编译器里,有一位暗黑破坏神,专门从事一份称作“名字粉碎”(name mangling)的工作。当把一个C++的源文件投入编译的时候,它就开始工作,把每一个它在源文件里看到的外部可见的名字粉碎的面目全非,然后存储到二进制目标文件的符号表里。 这也就是为什么C++能够实现静态多态

之所以在C++的世界里存在这样一个怪物,是因为C++允许对一个名字给予不同的定义,只要在语义上没有二义性就好。比如,你可以让两个函数是同名的,只要它们的参数列表不同即可,这就是函数重载(function overloading);甚至,你可以让两个函数的原型声明是完全相同的,只要它们所处的名字空间(namespace)不一样即可。事实上,当处于不同的名字空间时,所有的名字都是可以重复的,无论是函数名,变量名,还是类型名。

另外,C++程序的构造方式仍然继承了C语言的传统:编译器把每一个通过命令行指定的源代码文件看做一个独立的编译单元,生成目标文件;然后,链接器通过查找这些目标文件的符号表将它们链接在一起生成可执行程序。

编译和链接是两个阶段的事情;事实上,编译器和链接器是两个完全独立的工具。编译器可以通过语义分析知道那些同名的符号之间的差别;而链接器却只能通过目标文件符号表中保存的名字来识别对象。

所以,编译器进行名字粉碎的目的是为了让链接器在工作的时候不陷入困惑,将所有名字重新编码,生成全局唯一,不重复的新名字,让链接器能够准确识别每个名字所对应的对象。

C语言却是一门单一名字空间的语言,也不允许函数重载,也就是说,在一个编译和链接的范围之内,C语言不允许存在同名对象。比如,在一个编译单元内部,不允许存在同名的函数,无论这个函数是否用static修饰;在一个可执行程序对应的所有目标文件里,不允许存在同名对象,无论它代表一个全局变量,还是一个函数。所以,C语言编译器不需要对任何名字进行复杂的处理(或者仅仅对名字进行简单一致的修饰(decoration),比如在名字前面统一的加上单下划线_)所以C就不要对名字进行复杂的处理,也就是是什么就是什么

C++的缔造者Bjarne Stroustrup在最初就把C++能够兼容C,能够复用大量已经存在的C库列为C++语言的重要目标。但两种语言的编译器对待名字的处理方式是不一致的,这就给链接过程带来了麻烦。

例如,现有一个名为my_handle.h的头文件,内容如下:

然后使用C语言编译器编译my_handle.c,生成目标文件my_handle.o。由于C语言编译器不对名字进行粉碎,所以在my_handle.o的符号表里,这三个函数的名字和源代码文件中的声明是一致的

这个时候,.o就相当于C的文件,里面的符号表就长这样,这个时候C++的目的就是说能够正常的使用这些C的库文件,如果你在C++的文件调用了C的API那么问题就大了,C+会用复杂的处理规则把参数加入名称,这样生成出来的名称放在符号表里面就是和C的不一样的,那么链接C的库文件自然就不行

随后,我们想让一个C++程序调用这些函数,所以,它也包含了头文件my_handle.h。假设这个C++源代码文件的名字叫my_handle_client.cpp,其内容如下:

其中,粗体的部分就是那三个函数的名字被粉碎后的样子。符号表

然后,为了让程序可以工作,你必须将my_handle.omy_handle_client.o放在一起链接。由于在两个目标文件对于同一对象的命名不一样,链接器将报告相关的“符号未定义”错误

为了解决这一问题,C++引入了链接规范(linkage specification)的概念,表示法为extern"language string",C++编译器普遍支持的"language string"有"C"和"C++",分别对应C语言和C++语言。链接规范的作用是告诉C++编译:对于所有使用了链接规范进行修饰的声明或定义,应该按照指定语言的方式来处理,比如名字,调用习惯(calling convention)等等。链接规范的用法有两种:
- 单个声明的链接规范,比如:

extern “C” void foo();

  • 一组声明的链接规范,比如:
extern "C"
{
  void foo();
  int bar();
}

为什么不把include放进extern C中

因为可以include文件里面还有extern C存在,这样就造成了extern C的嵌套,当嵌套发生以内层为准这样就会出现问题

参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值