C/C++知识点之C、C++混合调用
新开发者(IT开发者)
本文主要向大家介绍了C/C++知识点之C、C++混合调用,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。
在项目中,C和C++代码相互调用是很常见的,但在调用时,究竟应该如何编写代码和头文件,有一些讲究,不然就可能出现编译时链接不通过的问题,典型的编译错误日志是:
undefinedreferenceto`xxx' 要编写出C或C++都能正常调用的代码,需要明白编译器在编译时,究竟做了什么。下面就以几段简单的代码为例,来说明一下GCC系列编译器在编译C、C++代码时,分别做了什么,我们该如何编写自己的函数库以供C和C++代码调用。
本文验证的环境是:UbuntuServer18.04LTS,gcc/g++7.3.0,nm2.30
C函数库如何被C和C++代码调用 sum.c是一个使用C代码编写的对整数求和函数,代码非常简单:
复制代码 1#include"sum.h" 2 3intsum(inta,intb) 4{ 5returna+b; 6} 复制代码 在不考虑C++的调用时,头文件sum.h通常会按照如下写法:
复制代码 1#ifndef__SUM_H__ 2#define__SUM_H__ 3 4intsum(inta,intb); 5 6#endif/*__SUM_H__*/ 复制代码 我们编写下面的main.cpp代码来调用它:
复制代码 1#include<iostream> 2 3#include"sum.h" 4 5intmain(void) 6{ 7std::cout<<sum(1,1)<<std::endl; 8 9return0; 10} 复制代码 编译并运行一下看看:
$g++-omainmain.cppsum.c $./main 2 从结果来看,没有任何问题,程序正常编译通过和执行。
但是,如果sum.c是要做成一个库文件,可供C或C++代码调用时,又该如何呢?以静态库为例,sum.c是先编译成.o文件,再和其它的同类文件一起打包到.a中,由于只有一个文件,这里就不把它打包到.a文件了,仅把它生成一个.o文件作为函数库看一下:
$gcc-csum.c $g++-omainmain.cppsum.o /tmp/ccNnKecX.o:Infunction`main': main.cpp:(.text+0xf):undefinedreferenceto`sum(int,int)' collect2:error:ldreturned1exitstatus 从上述输出可以看到,链接是不能通过的。编译器告诉我们,这个引用没有定义。但我们都知道,sum函数是真实存在的。出现这种问题的原因,在于C++是支持面向对象的,函数可以重载,为了支持重载,编译时生成的.o文件中,函数名称不会像源文件那样。用nm列出目标文件中的符号,就可以看到真实的情况:
复制代码 $nmsum.o 0000000000000000Tsum $g++-cmain.cpp $nmmain.o U__cxa_atexit U__dso_handle U_GLOBAL_OFFSET_TABLE_ 0000000000000086t_GLOBAL__sub_I_main 0000000000000000Tmain U_Z3sumii 000000000000003dt_Z41__static_initialization_and_destruction_0ii U_ZNSolsEi U_ZNSolsEPFRSoS_E U_ZNSt8ios_base4InitC1Ev U_ZNSt8ios_base4InitD1Ev U_ZSt4cout U_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 0000000000000000r_ZStL19piecewise_construct 0000000000000000b_ZStL8__ioinit 复制代码 从上面的输出中,我们可以看到,用gcc命令编译生成的sum.o,包含有一个符号sum。但是用g++编译出来的main.o,它里面引用到sum函数时,用的名字其实是_Z3sumii,这个就是g++编译器对C++代码做的处理。由于sum.o中并没有_Z3sumii函数,链接当然要失败。
那么,为什么一开始的命令g++-omainmain.cppsum.c能正常生成可执行文件呢,因为在这条命令执行时,对sum.c也当作是C++代码来处理的,我们可以对sum.c调用g++命令来验证一下:
$g++-csum.c $nmsum.o 0000000000000000T_Z3sumii 可见,对于C源文件,调用g++命令时,是把C代码视作C++来处理的。对于C++文件去调用gcc命令,又当如何呢?有兴趣的可以自行尝试一下,这里就不再展开了,仅给出一个规则:
对于C代码,使用gcc命令去编译,让编译器按C代码的规则来处理,这样便于其它C代码调用,如果按照C++代码处理了,虽然C++调用是方便了,但其它C代码调用就麻烦了 对于C++代码,使用g++命令编译,按C++的规则处理 知道了编译器会做什么后,我们看一下如何让C++调用C函数库。查看系统库的标准头文件,我们会看到很多这样的代码:
复制代码 1#ifdef__cplusplus 2extern"C"{ 3#endif 4... 5#ifdef__cplusplus 6} 7#endif 复制代码 其实,extern"C"就是告诉编译器,使用extern"C"修饰的代码是用C的目标文件格式来编译的,这样符号名称就是按照C的命令规则去查找和生成。extern"C"有两种形式,一种是修饰单行语句的,例如:
extern"C"intsum(inta,intb); 这种形式可以单独写在某个源代码文件中,但不常见。另一种是对整块代码做修饰,例如:
extern"C"{ intsum(inta,intb); ... } 为什么系统头文件要加#ifdef__cplusplus呢,因为C编译器不认识extern"C",如果插入这个,C编译器就要报错,所以,只应该对C++代码这么定义,由于在编译C++代码时,编译器会自动定义宏__cplusplus,因此,就可以利用这个宏来做条件编译。现在,我们把sum.h改造成:
复制代码 1#ifndef__SUM_H__ 2#define__SUM_H__ 3 4#ifdef__cplusplus 5extern"C"{ 6#endif 7 8intsum(inta,intb); 9 10#ifdef__cplusplus 11} 12#endif 13 14#endif/*__SUM_H__*/ 复制代码 然后,重新使用原来报错的命令试试:
$gcc-csum.c $g++-omainmain.cppsum.o $./main 2 这样就成功了。总结下来,就是C头文件中,加上extern"C"{}这样的声明,并对C代码仍是按C语言的方式编译,这样做,C或C++代码调用都没有问题。 extern"C"还有另一个用法,有些已经存在的C函数库及其头文件,并没有做这样的处理,那C++代码又当如何引用呢?答案是在C++代码中,按照如下方式编写代码:
extern{ #include"C_header.h" } C代码如何调用C++的函数 这里,仍旧使用相同的示例来说明,只是反过来,sum.cpp如下:
复制代码 #include"sum.h"
intsum(inta,intb) { returna+b; } 复制代码 sum.h如下:
复制代码 #ifndef__SUM_H__ #define__SUM_H__
#ifdef__cplusplus extern"C"{ #endif
intsum(inta,intb);
#ifdef__cplusplus } #endif
#endif/*__SUM_H__*/ 复制代码 main.c如下:
复制代码 1#include<stdio.h> 2 3#include"sum.h" 4 5intmain(void) 6{ 7printf("%d\n",sum(1,1)); 8 9return0; 10} 复制代码 编译运行结果如下:
复制代码 $g++-csum.cpp $nmsum.o 0000000000000000Tsum $gcc-omainmain.csum.o $./main 2 复制代码 从这里仍可以看到,extern"C"在起作用,如果把extern"C"的部分去掉,再编译时,就会看到链接不过的提示:
复制代码 $g++-csum.cpp $nmsum.o 0000000000000000T_Z3sumii $gcc-omainmain.csum.o /tmp/ccJkz0dn.o:Infunction`main': main.c:(.text+0xf):undefinedreferenceto`sum' collect2:error:ldreturned1exitstatus 复制代码 解决此问题的最简单办法,就是对main.c调用g++命令,由于C++对C的兼容性,这样做完全不成问题:
$g++-omainmain.csum.o $./main 2 当然,这只适用于非成员函数,如果想在C代码中调用成员函数,由于涉及到类,需要额外的包装,这里,我们把这个函数放在一个类Math里,作为一个静态成员函数来演示,非静态成员函数的情况更麻烦,建议直接使用C++代码来处理后,再包装成静态成员的方式做转换,示例的math.h:
1classMath{ 2public: 3staticintsum(inta,intb); 4}; 示例的math.cpp:
复制代码 1#include"math.h" 2 3intMath::sum(inta,intb) 4{ 5returna+b; 6} 复制代码 编写的包装器头文件math_wrapper.h:
复制代码 1#ifndef__MATH_WRAPPER_H__ 2#define__MATH_WRAPPER_H__ 3 4#ifdef__cplusplus 5extern"C"{ 6#endif 7 8intsum(inta,intb); 9 10#ifdef__cplusplus 11} 12#endif 13 14#endif/*__MATH_WRAPPER_H__*/ 复制代码 包装器代码math_wrapper.cpp:
复制代码 1#include"math.h" 2#include"math_wrapper.h" 3 4intsum(inta,intb) 5{ 6returnMath::sum(a,b); 7} 复制代码 编译上述代码并查看符号:
复制代码 $g++-cmath.cpp $nmmath.o $g++-cmath_wrapper.cpp $nmmath_wrapper.o U_GLOBAL_OFFSET_TABLE_ 0000000000000000Tsum U_ZN4Math3sumEii 复制代码 我们可以看到,math_wrapper.o把符号成功地转换了,写个main.c调用一下:
复制代码 #include<stdio.h>
#include"math_wrapper.h"
intmain(void) { printf("%d\n",sum(1,1));
return0; } 复制代码 编译运行毫无问题:
$gcc-omainmain.cmath_wrapper.omath.o $./main 2 基本思路就是:对已经按照C++的方式生成的C++库,用C++写个包装器来引用它的函数,但命名规则使用C的方式处理,这样就把函数转换成C代码可以调用的了,如果函数重载了,就写多个函数来转换。
本文由职坐标整理并发布,了解更多内容,请关注职坐标编程语言C/C+频道!
你的回复
回复请先 登录 , 或 注册相关内容推荐
最新讨论 ( 更多 )
- 【资讯】十多年来,使用过C ++、Ruby、Java语言等多种语言开... (新开发者)
- 学术访谈招募 (废墟上的阅读者)
- 5分钟教会你,QML如何通过WebSocket和C++语言交互? (新开发者)
- 人工智能前沿学生论坛60期| 知识图谱专场 (Jarvis)
- Python 实现曲线点抽稀算法 (新开发者)