【C++入门】C和C++混合编程超详细讲解_c++编程-程序员宅基地

技术标签: 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 ./
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42031299/article/details/126688788

智能推荐

Docker使用(三) — Docker部署安装Nginx_docker安装nginx-程序员宅基地

文章浏览阅读2.4w次,点赞9次,收藏79次。Docker部署安装Nginx_docker安装nginx

c语言编写显示一行字程序,C语言文件操作——读一行和写一行-程序员宅基地

文章浏览阅读1.2k次。文件读写其实说简单也简单,会用即可,说复杂也复杂,操作系统文件系统有很多东西需要学习。在此简单介绍一种写日志与读日志的方法——写一行&读一行。1.写一行代码:#include void writeLogLine(const char *fileName,const char *content)//参数:文件全名,写入内容{FILE *fp;//定义文件指针if ((fp=fopen(fil..._c写文件循环一次写一行

315M发射电路原理以及315M无线模块怎么使用_315m 放大电路-程序员宅基地

文章浏览阅读1.4k次,点赞26次,收藏24次。315M发射电路原理以及315M无线模块怎么使用_315m 放大电路

记账系统推荐金蝶精斗云_金蝶精斗云是免费会计记账软件吗?-程序员宅基地

文章浏览阅读1.5k次。金蝶精斗云旗下的财务软件有很多款,其中有一款云报销的产品有免费版,不过金蝶精斗云的云报销虽然是永久免费的,但是功能和权限相对也会更少一些,所以不一定适合你公司。金蝶精斗云旗下产品针对不同的行业、企业的不同规模,产品各有侧重点,可以根据你公司的实际需要挑选,每一款产品根据功能的多寡,价格也是不一样的。金蝶精斗云财务软件是按年收费的。金蝶精斗云旗下的产品包括云会计(财务管理工具),云进销存(采购、销售..._精斗云永久免费

MyBatis批量插入数据_批量提交insrt-程序员宅基地

文章浏览阅读396次。mybatis批量插入数据_批量提交insrt

拥抱.NET Core系列:MemoryCache 初识 (转载)-程序员宅基地

文章浏览阅读52次。阅读目录MSCache能做什么?从IMemoryCache说起开发者的体验写在最后 Cache是一个绝大多数项目会用到的一个技术,说起到缓存可能就联想到 Set、Add、Get、Remove、Clear 这几个方法。那么在.NET Core中微软给我们带来了什么样的缓存体验呢?今天我们就来介绍下”Microsoft.Extensions.Caching.Memory...__memorycache.getorcreate absoluteexpirationrelativetonow 需要与set一起使用嘛

随便推点

@FeignClient url读取配置文件,为空时使用服务名-程序员宅基地

文章浏览阅读8k次,点赞4次,收藏2次。FeignClient url_@feignclient url

Shader Graph 笔记,持续更新..._shader graph 毛刺效果-程序员宅基地

文章浏览阅读4.8k次,点赞17次,收藏81次。第一次接触Shader Graph 是在18年7月份Shader Graph最佳实践 (Best practices for Shader Graph - Unite 2018 洛杉矶)Unity着色器Procedural绘图简介(非Node/代码逻辑)原解读Unity2018中 ShaderGraph 示例: Procedural[Shader Graph备忘录1]扫描线..._shader graph 毛刺效果

性能测试实战(四):Jmeter的使用_jmeter默认保存路径-程序员宅基地

文章浏览阅读9.1k次,点赞2次,收藏9次。一、初识Jmeter1、Jmeter的文件结构:(1)binjmeter的启动文件以及配置文件。jmeter的默认保存文件路径,也是在jmeter的bin文件夹里;读取文件,默认也是该文件夹。(2)docs文档,开发人员看的接口api文档,如果你要对jmeter进行二次开发,需要阅读此文档。(3)printable_docs文档,这是Jmeter的使用帮助文档(离线帮助)。(4)liblibrary库,存放jmeter源码打成的jar包,以及第三方人员开发的扩展功能jar_jmeter默认保存路径

The FactorySoft OPC Server _组态王fsserver-程序员宅基地

文章浏览阅读2.4k次。The FactorySoft OPC Server 最近在看opc的东西,一开始看的是潘爱民的《COM原理与应用》,这本书买了老早了,几年前领导想在在组态王6.03里面添加我们公司的设备的驱动,到北京组态王的总部去了一趟,没接触过怎么做组态王的驱动,请教了接待我们的MM经理,她说很简单,用到了COM和C++,下午就去了书店买了回来,后来安装上开发包研究了下,书也没看就把驱动写完了,一直放到现在。 后来做工控的上位,西门子的PLC使用的最多,所以_组态王fsserver

Linux:字符串数组和字符串指针的定义赋值操作_linux字符串赋值-程序员宅基地

文章浏览阅读514次。【代码】Linux:字符串数组和字符串指针的定义赋值操作。_linux字符串赋值

python实战案例_定义一个movie_score()类,包含封装change_movie()类和get_score()-程序员宅基地

文章浏览阅读1.4w次,点赞6次,收藏14次。博主介绍 个人主页:苏州程序大白 个人社区:CSDN全国各地程序猿作者介绍:中国DBA联盟(ACDU)成员,CSDN全国各地程序猿(媛)聚集地管理员。目前从事工业自动化软件开发工作。擅长C#、Java、机器视觉、底层算法等语言。2019年成立柒月软件工作室,2021年注册苏州凯捷智能科技有限公司 有任何问题欢迎私信,看到会及时回复 微信号:stbsl6,微信公众号:苏州程序大白 如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连) 想加入技术交流群的可以_定义一个movie_score()类,包含封装change_movie()类和get_score()类,并接收用户的

推荐文章

热门文章

相关标签