混合编程
-
为什么需要混合编程
(1)C有很多优秀成熟项目和库,丢了可惜,重写没必要,C++程序里要调用
(2)庞大项目划分后一部分适合用C,一部分适合用C++
(3)其他情况,如项目组一部分人习惯用C,一部分习惯用C++ -
为什么不同语言可以混合编程
(1)程序编译过程:源文件->目标(库)文件->可执行程序->镜像文件
(2)任何编程语言执行时都必须是可执行程序,所以都必须先被编译成目标文件
(3)混合编程的“混合”操作发生在链接这一步 -
C++和C混合编程的困难所在
(1)C++和C都是编译型语言,互相混合相对容易
(2)难点:C++支持函数名重载,而C不支持,因此编译器生成目标文件时,函数名在目标文件中的临时内部名称规则不同。导致链接时符号对不上
(3)解决方案:使用extern “C”{}; 让C++在对接的局部向C妥协兼容
通用解决方案:在C的头文件中加extern "C"声明,在C++中直接包含头文件调用即可 -
使用objdump工具来研究函数编译后的符号
(1)写个典型的C语言库mylib.c和mylib.h,提供add和sub等几个函数
(2)使用gcc -c -o编译得到库文件,再objdump -d反汇编得到.i文件
(3)对比加不加extern "C"这2种情况下得到的.i文件的符号差异
实验第1步:证明了C语言中名称为add的函数,编译后符号表中就叫add
gCC -c clib.c -o clib.o
objdump -d clib.o > clib.i
gCC -c clib.c -o clib2.o
objdump -d clib2.o > clib.i
实验第2步:证明了C++语言中名称为add的函数,编译后符号表中叫_Z3addii
分析:同样的源码,编译后生成的二进制代码其实是一样的,所以功能其实也是一样的
所以本质上是可以混合编程的,但是生成的中间符号名称不同,所以链接器难受
实验第3步:证明了在C++的头文件中,只要把C++的函数的声明放在extern “C”{}的大括号范围之内,就可以让g++在编译这个函数时生成中间符号名时按照C的规则而不是按照C++的规则,所以这样的函数就可以和C的库进行共同链接。
extern "C" {
void fun();
}
#if __cplusplus
extern "C" {
#endif#if __cplusplus
}
#endif
-
#if __cplusplus: 这是一个预处理器指令,用于检查当前代码是否在 C++ 环境中编译。__cplusplus 是一个预定义的宏,它在编译 C++ 代码时被定义为一个年份值,比如 199711L 或更高。因此,#if __cplusplus 的作用是在编译时判断是否为 C++ 环境。
-
extern “C”: 这是 C++ 提供的一种语法,用于告诉编译器按照 C 的方式对待包裹在其中的代码。在 C++ 中,函数名会被编译器进行名称修饰(name mangling),以支持函数重载和命名空间等特性。而 C 中没有这些特性,函数名不会进行修饰。因此,当 C++ 调用 C 的函数时,需要使用 extern “C” 来告诉编译器按照 C 的方式来处理函数名,以便在链接时能够正确找到对应的函数。
-
#endif: 这是预处理器指令,表示条件编译的结束。与 #if 配对使用,用于结束条件编译的代码块
预编译
g++ -E main.cpp -o main.i
生产静态库
ar -r libclib.a clib.o
g++ main.cpp -lclib -L.
C调用C++库的方法
代码实战:C调用C++库中的函数
构建C++库
gcc cppadd.cpp -c -o cppadd.o
ar -r libcppadd.a cppadd.o
反编译查看信息
objdump -d libcppadd.a > libcppadd.i
test.c
在这里插入代码片
extern int _Z3addii(int a,int b);int main(void)
{_Z3addii(1,2);return 0;
}
gcc test.c -lcppadd -L.
解决方案:添加一层封装层
g++ cppaddwrapper.cpp -c -o cppaddwrapper.o -lcppadd -L.
ar -r libcppaddwrapper.a cppaddwrapper.o
objdump -d libcppaddwrapper.a > cppaddwrapper.i
gcc test.c -lcppaddwrapper -lcppadd -L.
因为一开始的add是这样编写的
int add(int a, int b) {cout << "a + b = " << a + b << endl;return 0;
}
#include "cppadd.hpp"的引入放在cppaddwrapper.cpp
还是会出现报错。提示有东西没有引入
/usr/bin/ld: ./libcppadd.a(cppadd.o): warning: relocation against
_ZSt4cout' in read-only section
.text’
/usr/bin/ld: ./libcppadd.a(cppadd.o): in functionadd(int, int)': cppadd.cpp:(.text+0x1f): undefined reference to
std::cout’
/usr/bin/ld: cppadd.cpp:(.text+0x27): undefined reference tostd::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)' /usr/bin/ld: cppadd.cpp:(.text+0x3c): undefined reference to
std::ostream::operator<<(int)’
/usr/bin/ld: cppadd.cpp:(.text+0x43): undefined reference tostd::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)' /usr/bin/ld: cppadd.cpp:(.text+0x4e): undefined reference to
std::ostream::operator<<(std::ostream& (*)(std::ostream&))’
/usr/bin/ld: ./libcppadd.a(cppadd.o): in function__static_initialization_and_destruction_0(int, int)': cppadd.cpp:(.text+0x85): undefined reference to
std::ios_base::Init::Init()’
/usr/bin/ld: cppadd.cpp:(.text+0xa0): undefined reference to `std::ios_base::Init::~Init()’
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
#include "cppadd.hpp"的引入放在cppaddwrapper.cpp
还是会出现报错。提示有东西没有引入
/usr/bin/ld: ./libcppaddwrapper.a(cppaddwrapper.o): in function
addwrapper': cppaddwrapper.cpp:(.text+0x1d): undefined reference to
add’
collect2: error: ld returned 1 exit status
总结
理解混合编程的存在性,知道解决方法
学习记录,侵权联系删除。
来源:朱老师物联网大课堂