C++入门C和C++混合编程超详细讲解

Posted 正在起飞的蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门C和C++混合编程超详细讲解相关的知识,希望对你有一定的参考价值。

1、为什么需要C和C++混合编程

(1)C++是在C语言的基础上发展出来的,在C++发明之前已经用C语言实现了很多功能库,C++要使用这些库就涉及到调用C语言;
(2)C++和C语言各自有更适合的领域,在大型项目中都需要使用,涉及到C和C++的互相调用。比如嵌入式设备开发中,应用层开发、算法开发中使用C++,底层的操作系统、音视频开发使用C语言;

2、为什么不同的语言可以混合编程

(1)程序编译的过程:高级语言->汇编语言->二进制代码->链接->可执行程序->烧录镜像;
(2)任何语言最终都要变成二进制的可执行程序才能在CPU上运行,不同的高级语言在编译成汇编语言的过程是不一样的,所以每种高级语言都有自己的编译器;
(3)不管哪种高级语言在汇编语言阶段或者二进制代码阶段时都是一样的格式,互相之间是可以调用的,也就是我们常看到的库文件,开发中常把代码编译成库供其他人调用;
(4)高级语言的目标都是编译成汇编指令,汇编指令是和CPU相关而与高级语言无关,所以CPU相同的情况下,每种高级语言在汇编阶段都是统一的;

3、C和C++混合编程的困难

3.1、C++的函数重载带来的麻烦

(1)C和C++之间互相调用是根据函数名进行,在链接时通过函数名将函数对应的二进制代码链接起来;
(2)C++支持函数重载:C++中可以有相同名字的函数,只要同名函数的传参不同,C++就不会报错,并根据调用时的传参来选择调用对应的函数;
(3)在符号表中,C语言的函数名是不考虑传参的,但是C++的函数名是和传参类型有关的,这就导致通一个函数在C和C++中函数名不一致,也就会导致在链接时找不到函数的报错;

3.2、验证C++的函数重载机制

3.2.1、C++代码

//cppTest.cpp

int add(int a, int b)

	
	return a+b;
	


float add(float a, float b)

	return a+b;

3.2.2、C代码

//cTest.c

int add(int a, int b)

	
	return a+b;
	

3.2.3、编译成汇编代码

//得到c代码对应的汇编代码
gcc -c cTest.c -o cTest.o
objdump -d cTest.o > cTest.i

//得到c++代码对应的汇编代码
g++ -c cppTest.c -o cppTest.o
objdump -d cppTest.o > cppTest.i

3.2.4、实验结果分析

(1)C++支持函数重载,所以可以在C++代码中定义两个同名但传参不同的函数,函数名分别叫_Z3addii和_Z3addff,其中最后两个字母和传参有关,ii表示两个传参都是int型,ff表示两个传参都是float型;
(2)通过对比,在C和C++中int add(int a, int b)函数的汇编代码都是一样的,不同的是函数名称,在C语言中是add,在C++中是_Z3addii;
(3)经过实现可知,如果不经过任何处理,虽然C和C++都定义了同样的add函数,但是经过编译后得到的汇编代码中函数名称却不同,这会导致链接时找不到函数;

4、解决函数重载带来的不兼容性

4.1、解决思路

(1)函数重载机制是C++的特性,而C语言并没有,根据向前兼容原则,需要C++去兼容C,而不能要求C语言去支持函数重载机制;
(2)解决方法就是C++在需要和C对接的局部不采用函数重载机制,向C兼容;
(3)在C++中,用extern “C”括起来的内容表示向C兼容,不要使用函数重载机制;
注意点:extern “C”是C++中支持的,在C中是没有extern “C”这个用法的,C语言使用编译会报错;

4.2、extern “C”使用示例

#ifdef __cplusplus
extern "C"
#endif

	······

#ifdef __cplusplus

#endif

(1)__cplusplus是C++中的宏,表示当前是C++的编译环境;
(2)上面实现的效果就是在C++的编译环境中就使用extern “C”向C语言兼容,如果不是C++的编译环境就不使用extern “C”;

5、混合编程的情况

5.1、C++调用C:C是库

5.1.、C函数代码:cTest.c

int add(int a, int b)

	
	return a+b;
	

5.1.2、C头文件:cTest.h

#ifndef __CTEST_H__
#define __CTEST_H__

#ifdef __cplusplus
extern "C"
#endif

int add(int a, int b);

#ifdef __cplusplus

#endif

#endif

5.1.3、C++调用代码:cppTest.cpp

#include <iostream>
#include "cTest.h" 

using namespace std;

int main()

	int a = 1;
	int b= 2;
	
	cout << add(a, b) << endl;
	
	return 0;

5.1.4、编译链接代码

5.1.4.1使用extern “C”

[root#]$ ls
cppTest.cpp  cTest.c  cTest.h  
[root#]$ 
[root#]$ gcc -c cTest.c -o cTest.o
[root#]$ 
[root#]$ ar -r libcTest.a cTest.o	
ar: creating libcTest.a
[root#]$ 
[root#]$ g++ cppTest.cpp -L. -lcTest
[root#]$ 
[root#]$ ls
a.out  cppTest.cpp  cTest.c  cTest.h  cTest.o  libcTest.a
[root#]$ 
[root#]$ ./a.out 
3

5.1.4.2、不使用extern “C”

root#]$  g++ cppTest.cpp -L. -lcTest
/tmp/cc0Wckzc.o: In function `main':
cppTest.cpp:(.text+0x21): undefined reference to `add(int, int)'
collect2: ld returned 1 exit status

把cTest.h头文件中的extern “C”去掉,按照上面的步骤重新再编译执行一遍,会报add函数未定义的错误;

5.1.5、实验现象分析

root#]$ nm libcTest.a 
cTest.o:
0000000000000000 T add

(1)cTest.h头文件中没有extern “C”去修饰,则会按照函数重载机制去调用add函数,也就是按照_Z3addii符号去libcTest.a中查找函数;
(2)用nm命令可以看到,在链接得到的libcTest.a库中只有add名字的函数,所以会报add函数未定义的错误;

5.1.6、总结

用C语言写功能库代码时,需要对外提供的函数接口头文件用extern “C”括起来,将来库无论被C语言调用还是被C++调用都支持;

5.2、C调用C++:C++是库

5.2.1、C++函数代码:cppTest.cpp

#include "cppTest.hpp"

int add(int a, int b)

	
	return a+b;
	

5.2.2、C++头文件:cppTest.hpp

#ifndef __CPPTEST_HPP__
#define __CPPTEST_HPP__

int add(int a, int b);

#endif

5.2.3、C调用代码:cTest.c

#include <stdio.h>
#include "cppTest.hpp" 

int main()

	int a = 1;
	int b= 2;
	
	printf("a+b=%d\\n", add(a, b));
	
	return 0;

5.2.4、编译链接代码

[root#]$ g++ cppTest.cpp -c -o cppTest.o
[root#]$ 
[root#]$ ar -r libcppTest.a cppTest.o
ar: creating libcppTest.a
[root#]$ 
[root#]$ gcc cTest.c -L ./ -lcppTest
/tmp/cchDGkJK.o: In function `main':
cTest.c:(.text+0x21): undefined reference to `add'
collect2: ld returned 1 exit status
[root#]$ 
[root#]$ nm libcppTest.a 

cppTest.o:
0000000000000000 T _Z3addii
                 U __gxx_personality_v0

(1)在编译C语言的可执行程序时,报add未定义的错误,因为在C++实现的libcppTest.a库中并没有用extern “C”将对C提供的add函数括起来,这样在编译
add函数时就会用函数重载机制,最终add函数在符号表中是_Z3addii,C语言按照add符号去链接时就会找不到add函数;
(2)冷知识:如果C语言中,按照_Z3addii函数名去调用,是可以成功的;

5.3、解决方案一:直接改C++源码

5.3.1、C++头文件增加extern “C”

#ifndef __CPPTEST_H__
#define __CPPTEST_H__

#ifdef __cplusplus
extern "C"
#endif

int add(int a, int b);

#ifdef __cplusplus

#endif

#endif

5.3.2、编译链接得到可执行程序

[root#]$g++ cppTest.cpp -c -o cppTest.o
[root#]$
[root#]$ar -r libcppTest.a cppTest.o
[root#]$
[root#]$gcc cTest.c -L. -lcppTest -lstdc++
[root#]$
[root#]$ ./a.out 
a+b=3
[root#]$ nm libcppTest.a 

cppTest.o:
                 U __gxx_personality_v0
0000000000000000 T add

修改C++库的代码,将要被C调用的函数头文件用extern “C”括起来,兼容C的调用;

5.4、解决方案二:将C++库再封装一层

5.4.1、使用场景

(1)如果别人给你提供了C++写的代码库,但是写C++代码库的人并没有考虑被C调用的情况,所以给你的库版本并没有用extern “C”去兼容C调用;
(2)通常对方提供.so动态库,你在没有源码的情况下是无法修改源码并重新编译动态库的,但是你可以对动态库再封装一层,封装的接口用extern “C”去括起来;

5.4.2、封装的C++源文件:cppPack.cpp

#include "cppPack.hpp"
#include "cppTest.hpp"

int addPack(int a, int b)

	
	return add(a, b);
	

5.4.3、封装的C++头文件:cppPack.hpp

#ifndef __CPPPACK_HPP__
#define __CPPPACK_HPP__

#ifdef __cplusplus
extern "C"
#endif

int addPack(int a, int b);

#ifdef __cplusplus

#endif

#endif

5.4.4、C调用代码:cTest

#include <stdio.h>
#include "cppPack.hpp" 

int main()

	int a = 1;
	int b= 2;
	
	//这里调用封装的addPack函数,addPack函数内部就是调用的add函数
	printf("a+b=%d\\n", addPack(a, b));
	return 0;

5.4.4、编译链接代码

root@ubuntu:# g++ cppPack.cpp -c -o cppPack.o
root@ubuntu:# ar -r libcppPack.a cppPack.o
root@ubuntu:# gcc cTest.c -L ./ -lcppPack -lcppTest -I ./

6、推荐

我会在C++专栏持续根据更新C++相关的知识点,这里也给大家推荐一款学习C++的神器,我也是在用这一款神器在学习C++。
链接:学习神器跳转
如果你是想入门C++这门语言或者是找C++岗位的工作,都推荐你试试这个网站,里面有针对C++知识点的选择题、编程题,更有C++岗位的面试题

以上是关于C++入门C和C++混合编程超详细讲解的主要内容,如果未能解决你的问题,请参考以下文章

C++基础讲解第三期(超详细)每天更新哈,大家一起加油

C++黑马程序员 | c++教程从0到1入门编程笔记 | c++提高编程

[C++] 什么! 你说你不懂C++? 看这里C++基础超详细,看一眼就入门

C++快速扫盲(提升篇)

超详细的C++入门学习(命名空间,缺省参数,内联函数,函数重载等)

C++知识总结(内附超详细知识框架图)