C、C++混合调用
在项目中,C和C++代码相互调用是很常见的,但在调用时,究竟应该如何编写代码和头文件,有一些讲究,不然就可能出现编译时链接不通过的问题,典型的编译错误日志是:
undefined reference to `xxx'
要编写出C或C++都能正常调用的代码,需要明白编译器在编译时,究竟做了什么。下面就以几段简单的代码为例,来说明一下GCC系列编译器在编译C、C++代码时,分别做了什么,我们该如何编写自己的函数库以供C和C++代码调用。
本文验证的环境是:Ubuntu Server 18.04 LTS,gcc/g++ 7.3.0,nm 2.30
C函数库如何被C和C++代码调用
sum.c是一个使用C代码编写的对整数求和函数,代码非常简单:
#include "sum.h" int sum(int a, int b)
{
return a + b;
}
在不考虑C++的调用时,头文件sum.h通常会按照如下写法:
#ifndef __SUM_H__
#define __SUM_H__ int sum(int a, int b); #endif /* __SUM_H__ */
我们编写下面的main.cpp代码来调用它:
#include <iostream> #include "sum.h" int main(void)
{
std::cout << sum(, ) << std::endl; return ;
}
编译并运行一下看看:
$ g++ -o main main.cpp sum.c
$ ./main
从结果来看,没有任何问题,程序正常编译通过和执行。
但是,如果sum.c是要做成一个库文件,可供C或C++代码调用时,又该如何呢?以静态库为例,sum.c是先编译成.o文件,再和其它的同类文件一起打包到.a中,由于只有一个文件,这里就不把它打包到.a文件了,仅把它生成一个.o文件作为函数库看一下:
$ gcc -c sum.c
$ g++ -o main main.cpp sum.o
/tmp/ccNnKecX.o: In function `main':
main.cpp:(.text+0xf): undefined reference to `sum(int, int)'
collect2: error: ld returned exit status
从上述输出可以看到,链接是不能通过的。编译器告诉我们,这个引用没有定义。但我们都知道,sum函数是真实存在的。出现这种问题的原因,在于C++是支持面向对象的,函数可以重载,为了支持重载,编译时生成的.o文件中,函数名称不会像源文件那样。用nm列出目标文件中的符号,就可以看到真实的情况:
$ nm sum.o
T sum
$ g++ -c main.cpp
$ nm main.o
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
t _GLOBAL__sub_I_main
T main
U _Z3sumii
000000000000003d t _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_
r _ZStL19piecewise_construct
b _ZStL8__ioinit
从上面的输出中,我们可以看到,用gcc命令编译生成的sum.o,包含有一个符号sum。但是用g++编译出来的main.o,它里面引用到sum函数时,用的名字其实是_Z3sumii,这个就是g++编译器对C++代码做的处理。由于sum.o中并没有_Z3sumii函数,链接当然要失败。
那么,为什么一开始的命令g++ -o main main.cpp sum.c能正常生成可执行文件呢,因为在这条命令执行时,对sum.c也当作是C++代码来处理的,我们可以对sum.c调用g++命令来验证一下:
$ g++ -c sum.c
$ nm sum.o
T _Z3sumii
可见,对于C源文件,调用g++命令时,是把C代码视作C++来处理的。对于C++文件去调用gcc命令,又当如何呢?有兴趣的可以自行尝试一下,这里就不再展开了,仅给出一个规则:
- 对于C代码,使用gcc命令去编译,让编译器按C代码的规则来处理,这样便于其它C代码调用,如果按照C++代码处理了,虽然C++调用是方便了,但其它C代码调用就麻烦了
- 对于C++代码,使用g++命令编译,按C++的规则处理
知道了编译器会做什么后,我们看一下如何让C++调用C函数库。查看系统库的标准头文件,我们会看到很多这样的代码:
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
其实,extern "C"就是告诉编译器,使用extern "C"修饰的代码是用C的目标文件格式来编译的,这样符号名称就是按照C的命令规则去查找和生成。extern "C"有两种形式,一种是修饰单行语句的,例如:
extern "C" int sum(int a, int b);
这种形式可以单独写在某个源代码文件中,但不常见。另一种是对整块代码做修饰,例如:
extern "C" {
int sum(int a, int b);
...
}
为什么系统头文件要加#ifdef __cplusplus呢,因为C编译器不认识extern "C",如果插入这个,C编译器就要报错,所以,只应该对C++代码这么定义,由于在编译C++代码时,编译器会自动定义宏__cplusplus,因此,就可以利用这个宏来做条件编译。现在,我们把sum.h改造成:
#ifndef __SUM_H__
#define __SUM_H__ #ifdef __cplusplus
extern "C" {
#endif int sum(int a, int b); #ifdef __cplusplus
}
#endif #endif /* __SUM_H__ */
然后,重新使用原来报错的命令试试:
$ gcc -c sum.c
$ g++ -o main main.cpp sum.o
$ ./main
这样就成功了。总结下来,就是C头文件中,加上extern "C" {}这样的声明,并对C代码仍是按C语言的方式编译,这样做,C或C++代码调用都没有问题。
extern "C"还有另一个用法,有些已经存在的C函数库及其头文件,并没有做这样的处理,那C++代码又当如何引用呢?答案是在C++代码中,按照如下方式编写代码:
extern {
#include "C_header.h"
}
C代码如何调用C++的函数
这里,仍旧使用相同的示例来说明,只是反过来,sum.cpp如下:
#include "sum.h" int sum(int a, int b)
{
return a + b;
}
sum.h如下:
#ifndef __SUM_H__
#define __SUM_H__ #ifdef __cplusplus
extern "C" {
#endif int sum(int a, int b); #ifdef __cplusplus
}
#endif #endif /* __SUM_H__ */
main.c如下:
#include <stdio.h> #include "sum.h" int main(void)
{
printf("%d\n", sum(, )); return ;
}
编译运行结果如下:
$ g++ -c sum.cpp
$ nm sum.o
T sum
$ gcc -o main main.c sum.o
$ ./main
从这里仍可以看到,extern "C"在起作用,如果把extern "C"的部分去掉,再编译时,就会看到链接不过的提示:
$ g++ -c sum.cpp
$ nm sum.o
T _Z3sumii
$ gcc -o main main.c sum.o
/tmp/ccJkz0dn.o: In function `main':
main.c:(.text+0xf): undefined reference to `sum'
collect2: error: ld returned exit status
解决此问题的最简单办法,就是对main.c调用g++命令,由于C++对C的兼容性,这样做完全不成问题:
$ g++ -o main main.c sum.o
$ ./main
当然,这只适用于非成员函数,如果想在C代码中调用成员函数,由于涉及到类,需要额外的包装,这里,我们把这个函数放在一个类Math里,作为一个静态成员函数来演示,非静态成员函数的情况更麻烦,建议直接使用C++代码来处理后,再包装成静态成员的方式做转换,示例的math.h:
class Math {
public:
static int sum(int a, int b);
};
示例的math.cpp:
#include "math.h" int Math::sum(int a, int b)
{
return a + b;
}
编写的包装器头文件math_wrapper.h:
#ifndef __MATH_WRAPPER_H__
#define __MATH_WRAPPER_H__ #ifdef __cplusplus
extern "C" {
#endif int sum(int a, int b); #ifdef __cplusplus
}
#endif #endif /* __MATH_WRAPPER_H__ */
包装器代码math_wrapper.cpp:
#include "math.h"
#include "math_wrapper.h" int sum(int a, int b)
{
return Math::sum(a, b);
}
编译上述代码并查看符号:
$ g++ -c math.cpp
$ nm math.o
$ g++ -c math_wrapper.cpp
$ nm math_wrapper.o
U _GLOBAL_OFFSET_TABLE_
T sum
U _ZN4Math3sumEii
我们可以看到,math_wrapper.o把符号成功地转换了,写个main.c调用一下:
#include <stdio.h> #include "math_wrapper.h" int main(void)
{
printf("%d\n", sum(, )); return ;
}
编译运行毫无问题:
$ gcc -o main main.c math_wrapper.o math.o
$ ./main
基本思路就是:对已经按照C++的方式生成的C++库,用C++写个包装器来引用它的函数,但命名规则使用C的方式处理,这样就把函数转换成C代码可以调用的了,如果函数重载了,就写多个函数来转换。
C、C++混合调用的更多相关文章
- C、C++混合调用——博客收藏
C与C++接口相互调用:https://www.cnblogs.com/feige1314/p/7890982.html C.C++混合调用:https://www.cnblogs.com/xuany ...
- python的位置参数、关键字参数、收集参数,关键字收集参数混合调用问题
参数混合调用顺序用法: 函数中参数顺序为:普通参数,收集参数,关键字参数,关键字收集参数,其顺序不能颠倒,颠倒会报错. 普通参数.关键字参数可以有n个,对量没有具体要求,收集参数和关键字收集参数要么没 ...
- C和C++混合编程中的extern "C" {}
引言 在用C++的项目源码中,经常会不可避免的会看到下面的代码: 1 2 3 4 5 6 7 8 9 #ifdef __cplusplus extern "C" { #endif ...
- C中如何调用C++函数、类内函数 \混编\链接
在C中如何调用C++函数的问题,简单回答是将函数用extern "C"声明,当被问及如何将类内成员函数声明时,一时语塞,后来网上查了下,网上有一翻译C++之父的文章可以作为解答,遂 ...
- android90 bind方式启动服务service调用service里的方法
package com.itheima.banzheng; import com.itheima.banzheng.LeaderService.ZhouMi; import android.os.Bu ...
- Fortran与C混合编程(转自Ubuntu)
Fortran与C混合编程 由于 GNU 的 Fortran 和 C 语言二者的函数彼此可以直接相互调用,所以混合编程可以非常容易地实现.只要你足够仔细,确保函数调用时传递的参数类型正确,函数就可以在 ...
- 18_Android中Service的生命周期,远程服务,绑定远程服务,aidl服务调用,综合服务案例,编写一个应用程序调用远程支付宝远程服务场景
============================================================================ 服务的生命周期: 一.采用start的方式开始 ...
- delphi 调用 c++builder
delphi 调用 c++builder c++builder 调用delphi 混合调用,mix https://community.embarcadero.com/blogs/entry/mixi ...
- Android -- service的开启方式, start开启和绑定开启服务,调用服务的的方法, aidl调用远程服务
1. 概述 bindService() 绑定服务 可以得到服务的代理人对象,间接调用服务里面的方法. 绑定服务: 间接调用服务里面的方法. 如果调用者activity被销毁了, ...
随机推荐
- 电脑断电后Everything部分文件搜索不到的解决办法
常规检查:查看选项→索引→NTFS,确认所有分区都[包含到数据库],确认后,再删除数据库文件,点击[强制重建] 下面方法是亲身经历,是断电造成的,费了不少时间才解决,现分享出来: 断电后,Everyt ...
- hdu-3074 Multiply game---线段树+单点更新
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3074 题目大意: 给一些数,进行点更新或者是区间计算乘积 解题思路: 裸的线段树,注意空间开大点 # ...
- vs使用libevent
1.下载最新libevent-2.1.8-stable,并解压 2.使用vs2013 工具这里使用x64,这里更新一下,改为使用x86 进入到libevent目录 运行 nmake /f Makefi ...
- 理解HTML DOM
DOM(Document Object Model)全称文档对象模型.DOM其实是JavaScript操作网页的一套API接口,定义了访问和操作HTML文档的标准.定义了所有HTML元素的对象和属性, ...
- [转]SQL Server 安全性概論與無法刪除資料庫使用者的解決辦法
經常有人來問我特定 SQL Server 資料庫裡的使用者無法刪除的問題,這問題其實跟 SQL Server 的安全性架構有很大關係,解決這個問題當然還是瞭解觀念的重要性大於知道如何解決問題.除了講解 ...
- 如何在jsp页面获取系统时间
<%@ page import="java.util.*"%> //获取系统时间必须导入的 <%@ page import="java.text.*&q ...
- oracle11g的安装与卸载
1.首先确认电脑已安装 .NET Framework3.5 2.本地计算机的安装: 参考:https://www.cnblogs.com/hoobey/p/6010804.html 3.服务器的安装: ...
- 19-3-5Python中列表、元组、以及range
一.列表: 为什么要学列表? 因为字符串存在缺点: 1) 只能存储少量的数据. 2) 数据类型无论索引.切片 获取的都是字符串类型,类型过于单一,转化成它原来的类型还需要进一步转换 ...
- Zabbix——自动发现
前提条件: Zabbix版本为4.0 固定网段寻找网络设备,并添加组.添加模板.添加proxy. 设置完毕,等待~~ 如果没有问题,将会直接出现在host中.
- 配置SpringBoot方便的切换jar和war
配置SpringBoot方便的切换jar和war 网上关于如何切换,其实说的很明确,本文主要通过profile进行快速切换已实现在不同场合下,用不同的打包方式. jar到war修改步骤 pom文件修改 ...