extern “C”

参考文献:

1.http://blog.csdn.net/hxg130435477/article/details/6632954

2. http://www.firedragonpzy.com.cn/wp-content/uploads/2013/02/effective-c.pdf

1.问题定义


extern “C” 的作用是让 C++ 程序能够成功链接由 C 编译器编译生成的库文件或者是目标文件。

通常,在C 语言的头文件中经常可以看到类似下面这种形式的代码:

C代码   收藏代码
  1. #ifdef __cplusplus  
  2. extern "C" {  
  3. #endif  
  4.   
  5. /**** some declaration or so *****/  
  6.   
  7. #ifdef __cplusplus  
  8. }  
  9. #endif  
 

      其实extern “C”可以用在函数定义之前,也可以用在函数声明之前。这两者的区别在后续内容中将会讲到,但是一般用在函数声明之前。或许大家都知道,extern “C”的作用就是在C++环境中使函数按照C的标准来编译和链接,但这种说话不全面。比如说当extern “C”放在函数声明之前,就不会改变函数的编译方式,只是指定编译器按照C的标准链接,而不是按照C++的标准去链接函数。

2 .原理

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

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

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

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

       所以,编译器进行名字粉碎的目的是为了让链接器在工作的时候不陷入困惑,将所有名字重新编码,生成全局唯一,不重复的新名字,让链接器能够准确识别每个名字所对应的对象。但C语言却是一门单一名字空间的语言,也不允许函数重载,也就是说,在一个编译和链接的范围之内,C语言不允许存在同名对象。比如,在一个编译单元内部,不允许存在同名的函数,无论这个函数是否用static修饰;在一个可执行程序对应的所有目标文件里,不允许存在同名对象,无论它代表一个全局变量,还是一个函数。所以,C语言编译器不需要对任何名字进行复杂的处理(或者仅仅对名字进行简单一致的修饰(decoration),比如在名字前面统一的加上单下划线_)。


3 分析


3.1 VC中.c与.cpp的编译规则

问题:在C++编译环境(VC 6.0)中,工程文件中同时包含.c文件和.cpp文件,那么编译.c和.cpp按照什么编译规则来编译的,有什么区别?

解答:在VC 6.0工程文件中,可能含有.cpp文件和.c文件混合编译。在.c的源文件按照C的标准来编译,函数名之前加入”_”,并把“_函数名”放入到符号表中,以后链接时使用。.cpp的源文件按照C++的标准编译,会修改函数名,这个不同的编译器有不同的实现方式,在VC中,函数名被修改的有些乱,如下:

(新建一个控制台工程包含func.c和main.cpp两个文件,func.c中定义了void func()在main.cpp中调用)



编译链接:


会提示链接错误!

func.c的汇编代码


有上述汇编代码可见,func函数编译后,函数名为_func(), 可见.c文件在VC中采用的是标准C的编译方式。
main.cpp的汇编代码



而在main.cpp中func的函数吗编译成?func@@YAXXZ
所以在链接时会出现错误!(main.cpp编译后的调用函数名和func.c编译后的函数名对不上)

怎么解决这个问题呢?

很简单只需要在main.cpp中把函数声明改为void func();


再次编译,链接通过

现在再查看汇编代码

func.c的汇编代码不变,因为没有任何改动,而main.cpp的汇编代码如下:


可以看到func()名字编译成了_func和func.c中的名字一致了,所以不会有错误了

3.2   extern “C”放在函数声明前的作用

问题:在C++编译环境中,用了(extern “C”+函数声明)与(不用extern “C”+函数声明),区别在哪?

解答:用(extern “C”+函数声明),表明CPP此文件中的函数名称,以C的标准来改编(“_函数名”), 如果在CPP文件中函数声明前不用extern “C”,则采用C++的标准来改编。

3.3   extern “C”放在函数定义前的作用

问题:在cpp文件中在函数定义前加了extern “C”后,此的函数定义的编译方式是否会改变(按照c的编译方式编译还是按照c++的编译方式编译)?

解答: 会改变,如果extern “C”放在函数定义之前,则C++编译器使得函数按照C的标准来编译和链接函数。

3.4  关于C标准库函数的思考

C和C++都支持C标准库函数,那么.c和.cpp中对标准库函数调用时他们的函数名改编是怎么样的呢?

在上述工程里func.c和main.cpp里都调用了C的标准库函数printf

func.c的汇编代码和main.cpp汇编代码如下

func.c的汇编代码:


main.cpp的汇编代码:




可以发现printf名字都改编成了_printf,原因?

打开stdio.h可以发现


头文件有如下宏定义

  1. #ifdef __cplusplus  
  2. extern "C" {  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值