41

浅谈C/C++混合编程

 4 years ago
source link: https://www.tuicool.com/articles/z2IfA33
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

****如只想知道怎样就能实现C/C++混合编程而不深究为什么的话, 可以一拉到底直接看总结.****

首先, 在介绍C/C++混合编程之前, 先思考几个问题

1. C/C++混合编程是什么?

2. C/C++混合编程有什么用?

3. C/C++混合编程应该怎么实现?

下面, 简单讲讲我对C/C++混合编程的理解 :

1. C/C++混合编程是什么?

就像问题本身所说, C/C++混合编程也就是一个工程中, 在C函数中调用C++函数的方法, 在C++的函数中能够调用C函数的方法.

2. C/C++混合编程有什么用?

在我们日常开发中, 也许会遇到这么一些情况, 同事A, C非常牛逼, 但是对C++一窍不通; 同事B, C++信手拈来, 但是对C却满头雾水. 但是在工作中有这么一种需求, 同事A需要用到C++的方法, 同事B需要用到C的方法, 这怎么办?

没错, 最简单的就是, 同事A把C的代码写好, 然后同事B只管调用即可, 同理, 同事A只管调用同事B写好的C++代码, 各司其职, 提高工作效率.

3. C/C++混合编程应该怎么实现?

那么, 这混合编程究竟要怎么实现呢?

在介绍之前, 我们先简单了解下以下几个概念

1. 函数重载
2. C++的名字改编机制
3. extern 及 extern "C"

* 函数重载( Overloading )

C++和Java中的函数重载的定义一致,

即在相同的作用域内, C++允许多个函数名称相同, 而形参列表不同, 如下图所示 :

3uQBrmz.jpg!web

函数重载

然而大家有没有想过为什么C++支持函数重载, 而C却不支持函数重载呢?

这个就要涉及到C++的名字改编机制了. 请往下看~

* C++的名字改编机制

在C中,

void test(); // 该函数编译后编译器会对函数名称改写成 _test

AJNNZbN.jpg!web

C语言中的test()函数

BrQ7nyY.jpg!web

C语言编译器改名后的test()函数叫_test

ps: 不提供test()函数的实现是让Xcode链接的时候报错, 这样我们才能看清楚test()函数的真面目!

void test(int a); // 该函数编译后编译器改写函数名后依然是 _test

uQr6Vby.jpg!web

C语言中的test(int)函数

AfmYzqJ.jpg!web

C语言编译器改名后的test(int)函数依旧叫_test

在C++中,

void test(); // 该函数编译后编译器会对函数名称改写成 test()

jYFFRbA.jpg!web

C++编译器改名后的test()函数叫test()

void test(int a); // 该函数编译后编译器改写函数名后是 test(int)

YnYZNv2.jpg!web

C++编译器改名后的test(int)函数叫test(int)

ps : 有的系统的编译器会编译成 _test_int 这种格式, 名字改编机制只是一种思路, 并没有一种唯一的命名规范, 不同的编译器命名规范不同, 但是思路一致! 如下图所示 :

RbaEnir.jpg!web

编译器不同所产生的函数名可能不同

本文就举这几个例子, 大家可以自行尝试重载多几个函数, 然后动手试试看结果. 实践才是王道!

通过上面几个例子, 相信大家很容易就能知道为什么C++支持重载而C不支持重载了.

因为C++有名字改编机制而C没有!

所以在C中, 只要函数名相同, 不管你的形参列表如何南辕北辙, 编译器均会将其编译为同一函数名, 这样在程序执行过程中就会造成函数调用的二义性(也就是对于相同函数名的函数, 程序并不知道应该调用哪一个函数), 这是不允许的, 所以会报错.

然而对于C++而言, 尽管他们的函数名相同, 但是因为他们的形参列表不同, 编译器编译后实际上会为他们改名为不同名字的函数, 所以程序执行调用函数的时候并不会产生二义性, 因此C++允许函数重载.

这里扯一句题外话, C++的重载被认为不是多态, 因为多态是动态运行时对方法的绑定, 而C++的函数重载最多算是编译时的"多态". (这句话不一定正确, 请大家纠正)

* extern 及 extern "C"

extern相信大家比较熟悉, 它一般用来声明一个函数, 全局变量的作用域. extern告诉编译器, 其声明的函数和变量可以供本文件或者其他文件使用. 这里不再赘述.

extern "C" 中的C是什么意思呢?

这里的C不是指C语言这一门语言, 而是表示一种编译和链接的规约. C表示符合C语言的编译和连接规约的任何语言,如Fortran(公式翻译)、assembler(汇编语言)等。

注意 :

extern "C" 只是指定编译和链接的规约, 并不会影响语义, 所以在C++文件中该怎么写还得怎么写, 必须遵循C++的语法规范.

在C++源文件的语句前加上 extern "C" 的作用就是告诉编译器, 这一段代码按照类C的编译和链接规约来编译和链接(对, 也就是按照类C的函数命名规范编译)

小技巧 : 如果有多条语句需要extern "C", 可以用{ } 括住, 例如 :

iymYZj3.jpg!web

extern "C" 用法

那应该怎样使用extern "C" 来 实现C/C++混合编程呢?

1. C中调用C++的代码
2. C++中调用C的代码
3. C/C++互调

* C中调用C++的代码

废话不多说, 上代码.

RbaqAf2.jpg!web

C调用C++中定义的sum(int, int)函数

毫无疑问, 这段代码是链接不通过的, 为什么呢?

在C中, 编译器会将main函数中调用的sum函数编译为_sum, 然而远在那边写在C++文件中的sum函数则被编译为sum(int, int), 则链接的时候编译器会报找不到_sum函数的错误. 如下图

yiMnyiR.jpg!web

未定义的_sum函数

解决思路很明确, 只要确保函数在C和C++文件中编译链接的规约一样就OK了!

这时候extern "C" 就要闪亮登场了!

此时我们只需要在cpp文件中用extern "C" { } 把需要被C文件调用的函数包含即可, 如下图

rEnEbyy.jpg!web

编译成功, 输出结果正确

* C++中调用C的代码

FB3INfE.jpg!web

C++调用C的sum(int, int)函数

EniaieQ.jpg!web

同样, 因为编译链接函数命名规范不同导致找不到函数

此时, 我们不能像之前C调用C++的方法来解决问题, 原因是 extern "C" 并不能在C文件中使用, 况且C文件本来就遵循C的编译和链接规约, 就算能在.c文件中使用extern "C" 也无济于事.

此时就涉及到#include的作用了, 众所周知, #include相当于文本拷贝, 等于把在头文件中声明的C函数原封不动cpy到#include "zs.h"中, 如下图

YJvuYzA.jpg!web

#include

有的朋友可能就想到了, 既然函数声明已经被copy到了C++文件中, 只需要保证C++文件中的这段函数声明代码按照类C的编译, 链接规约进行编译和链接就能保证编译出的sum函数的名称与C文件中的函数名称保持一致了!

所以便有了以下方法

b2EV7fy.jpg!web

Done!

细心的朋友就会发现, 既然#include相当于文本拷贝, 那为何不把extern "C" 语句放到头文件中呢?

好的, 我们试试~

ZBZFzmR.jpg!web

果不其然, 运行成功

但是我很遗憾的告诉大家, 如果你这样做的话, 那么C文件就不能调用C文件的方法了!

因为C的编译器不支持 extern "C" 语法!

这里要引出一个宏, __cplusplus, 只要是C++文件, 编译器就会自动定义一个这样的宏, 我们就能利用这个宏做到C/C++的终极混编了!

UjIjaye.jpg!web

C调用C函数成功

mAF3iqE.jpg!web

C++调用C函数成功

总结

要想写一套C/C++均能调用的函数, 则必须按照C的方式编译 (因为C语言不支持C++, 而C++同时支持C/C++)

要实现C/C++混合编程其实很简单, 只需要在头文件加几行代码即可, 如下图

aEFbyaV.jpg!web

C/C++混合编程核心代码

有趣的是, Objective-C的函数编译命名规范与C语言一样, 由此可知如果要实现C/OC/C++混合编程, 跟C/C++编程是大同小异.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK