c++工程中调用c文件或c库的方法

文章目录

一.概述
二.c和c++代码不兼容性分析
    1.使用gcc方式编译并反汇编
    2.使用g++方式编译并反汇编
三.c++工程调用c的两种解决方式
    1.在编译时对.c和.cpp文件都使用g++编译
    2.分别使用gcc和g++编译.c和.cpp文件,在链接时做文章
四.总结

一.概述

我们在c++工程中经常会调用c工程文件或者时c库文件。这样就可能会造成c文件和c++文件不兼容性而报错,具体的不兼容原因在下下面会详谈。我们之所以会在c++工程中调用c文件或者库是有现实因素的,因为之前有大量成熟的代码都是用c语言写的,性能优异而没有必要用c++重写。因此怎样去调用c代码是需要解决的问题。好在c和c++都是编译型语言,因此使得互相调用变得更加简单。我们都知道编译过程会生成.o目标文件,c和c++都是如此,因此我们有了c和c++各自生成的.o文件,在链接阶段做文章就可以实现c和c++共同链接使用。
二.c和c++代码不兼容性分析

造成c和c++代码不兼容性的最大原因就是:在c++中有函数名重载机制,而c中没有该机制。为了实现函数重载,在c++中通过在编译生成.o文件的过程中做文章,实际上是将函数名编译成为另一个函数名符号放在.o中,因此实现了同一个重载函数在.o中是不同名的函数符号。而c程序中不存在函数重载,因此.o文件中的函数名符号和.c文件中的函数名完全一致,下面结合反汇编查看.o文件的方式来直观观察:
  首先定义一个test1.c文件如下:

	int add(int a,int b)
	{
		return a+b;
	}
	int sub(int a,int b)
	{
		return a-b;
	}

对于test1.c这样一个c文件既可以使用gcc编译它,也可以使用g++编译它,下面就分析两种编译方式各自生成的.o文件有什么不同:
1.使用gcc方式编译并反汇编

gcc -c test1.c -o test1.o				//只编译生成.o文件
objdump -d test1.o >test1.i				//反汇编方便查看.o文件

反汇编生成的test1.i文件如下:

test1.o: 文件格式 elf64-x86-64

Disassembly of section .text:

0000000000000000 <add>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	89 7d fc             	mov    %edi,-0x4(%rbp)
   7:	89 75 f8             	mov    %esi,-0x8(%rbp)
   a:	8b 55 fc             	mov    -0x4(%rbp),%edx
   d:	8b 45 f8             	mov    -0x8(%rbp),%eax
  10:	01 d0                	add    %edx,%eax
  12:	5d                   	pop    %rbp
  13:	c3                   	retq   

0000000000000014 <sub>:
  14:	55                   	push   %rbp
  15:	48 89 e5             	mov    %rsp,%rbp
  18:	89 7d fc             	mov    %edi,-0x4(%rbp)
  1b:	89 75 f8             	mov    %esi,-0x8(%rbp)
  1e:	8b 45 fc             	mov    -0x4(%rbp),%eax
  21:	2b 45 f8             	sub    -0x8(%rbp),%eax
  24:	5d                   	pop    %rbp
  25:	c3                   	retq   

可以很直观的看出生成的.o文件中add和sub函数使用的是原来的函数名作为符号。
2.使用g++方式编译并反汇编

g++ -c test1.c -o test1.o				//只编译生成.o文件
objdump -d test1.o >test1.i				//反汇编方便查看.o文件

反汇编生成的test1.i文件如下:

test1.o: 文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z3addii>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	89 7d fc             	mov    %edi,-0x4(%rbp)
   7:	89 75 f8             	mov    %esi,-0x8(%rbp)
   a:	8b 55 fc             	mov    -0x4(%rbp),%edx
   d:	8b 45 f8             	mov    -0x8(%rbp),%eax
  10:	01 d0                	add    %edx,%eax
  12:	5d                   	pop    %rbp
  13:	c3                   	retq   

0000000000000014 <_Z3subii>:
  14:	55                   	push   %rbp
  15:	48 89 e5             	mov    %rsp,%rbp
  18:	89 7d fc             	mov    %edi,-0x4(%rbp)
  1b:	89 75 f8             	mov    %esi,-0x8(%rbp)
  1e:	8b 45 fc             	mov    -0x4(%rbp),%eax
  21:	2b 45 f8             	sub    -0x8(%rbp),%eax
  24:	5d                   	pop    %rbp
  25:	c3                   	retq   

可以很直观的看出生成的.o文件中add和sub函数使用的已经不是原来的函数名了。分别变为了_Z3addii和_Z3subii。
  小结:正是因为在编译过程中c和c++编译器的不同处理,因此造成了c和c++程序的不兼容而无法直接调用。那么有什么办法可以使他们兼容呢?请继续看。
三.c++工程调用c的两种解决方式

在上面的分析得知由于编译生成.o文件的不一致导致c++无法直接调用c文件或者c库(因为c库本质上就是一堆.o文件的集合)。那么有以下两种方法来解决这个问题:
<1>在编译时对.c和.cpp文件都使用g++编译。(不常用)
<2>分别使用gcc和g++编译.c和.cpp文件,在链接时做文章。(调用C库常用)
1.在编译时对.c和.cpp文件都使用g++编译

要是想要使用g++直接编译c文件,那么一种就是直接把.c文件当成c++文件编译。这样可能会造成.c文件于.cpp文件的冲突,不使用。使用的还是将.c文件按照编译c文件的方式编译(也就是按照gcc方式编译),那么就需要使用到extern “C”{ }编译声明了。这个声明的就表示在{}内部的代码完全按照c程序的方式去编译和链接(即使使用的时g++编译器)。需要在.c和.h文件中都使用 extern “C”{ }的方式包含.c代码段。代码实现如下:

/************************test1.c***************************/
#ifdef __cplusplus
extern "C"{
#endif
	//add函数定义
	int add(int a,int b)
	{
		return a+b;
	}
	//sub函数定义
	int sub(int a,int b)
	{
		return a-b;
	}
#ifdef __cplusplus
}
#endif

/************************test1.h***************************/
#ifndef __TEST1
#define __TEST1

#ifdef __cplusplus
extern "C"{
#endif
	
int add(int a,int b);		//	add函数声明
int sub(int a,int b);		//	sub函数声明
	
#ifdef __cplusplus
}
#endif
	
#endif

/************************test2.cpp***************************/
#include <iostream>
#include "test1.h"
using namespace std;
int main(void)
{
	int a = 10;
	int b = 11;
	int c = add(a,b);
	cout<<"a+b="<<c<<endl;
}

编译:g++ test1.c test2.cpp 生成a.out可执行文件。
执行./a.out输出:a+b=21
编译和指向完全正确。
但是这种方式不常用,因为正常我们调用的都是别人使用gcc编译好的.o文件库,不会让我们自己编译源文件,因此引出第二种常用的方法。
2.分别使用gcc和g++编译.c和.cpp文件,在链接时做文章

先使用gcc编译器编译test1.c生成test1.o文件,在使用g++编译test2.cpp时将test1.o链接进入。直接结合代码实现分析如下:

/************************test1.c***************************/
//add函数定义
int add(int a,int b)
{
	return a+b;
}
//sub函数定义
int sub(int a,int b)
{
	return a-b;
}
/************************test1.h***************************/
#ifndef __TEST1
#define __TEST1

#ifdef __cplusplus
extern "C"{
#endif
	
int add(int a,int b);		//	add函数声明
int sub(int a,int b);		//	sub函数声明
	
#ifdef __cplusplus
}
#endif
	
#endif

/************************test2.cpp***************************/
#include <iostream>
#include "test1.h"
using namespace std;
int main(void)
{
	int a = 10;
	int b = 11;
	int c = add(a,b);
	cout<<"a+b="<<c<<endl;
}

可以看出和第一种方式源码的唯一不同之处就是test1.c中没有使用extern “C”{ }编译声明了,这是因为我们本就是要用gcc来编译它。但是test.h文件中还必须要extern “C”{ }声明,这是因为我们在c++中也必须按照c的方式要链接它。
编译:gcc -c test1.c -o test1.o //仅编译test1.c生成.o文件(多了的话就链接成库)
g++ test1.o test2.cpp // 使用g++编译test2.cpp时链接上test1.o
生成a.out可执行文件。执行./a.out输出:a+b=21
  编译指向完全正确,第二种方式作为更普遍的c++调用c文件或库的方式我们应该掌握,在实际开发中我们通常会拿到一个c库文件和.h头文件,比如mylib.a和mylib.h两个文件。我们要在c++工程中调用他们就需要保证.h文件中使用了extern “C”{ }声明。如果.h中没有声明就代表我们不能直接使用c++调用它,那么我们可以有两种方法改造:一是直接改造.h文件,在我们的.cpp工程中直接包含#include“mylib.h”;二是不改.h文件,在我们的调用中使用extern “C”{ }声明包含头文件extern “C”{ #include“mylib.h”;}。这两种方法的本质上都是一样的,那就是我们要以c的方式调用库中的c函数。
四.总结

在c++工程中调用c库是很常见的事情,注意要点就是在.h文件中使用extern “C”{ }声明包含c库中函数的声明。另外知道我们我们在编写c工程文件的时候要按照extern “C”{ }的方式来写,保不齐以后就会给c++调用。
————————————————

原文链接:https://blog.csdn.net/weixin_42700740/article/details/113095186

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值