C++学习——如何实现C++和C的混合编程(extern “C“详解)

以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。

一、问题引出

在 C++ 出现之前,很多实用的功能都是用 C 语言开发的,很多底层的库也是用 C 语言编写的,如果在 C++ 代码中可以兼容 C 语言代码,无疑能极大地提高 C++ 程序员的开发效率。但是在一个项目中,能否既包含 C++ 程序又包含 C 程序呢?

答案是可以的,但是要小心处理,因为C++ 和 C 在程序的编译、链接等方面都存在一定的差异,这些差异往往会导致程序运行失败。

比如下面是一个用 C++ 和 C 混合编程实现的实例项目:

myfun.h文件内容:

cpp 复制代码
void display();

myfun.c文件内容:

cpp 复制代码
#include <stdio.h>
#include "myfun.h"
void display(){
   printf("C++:http://c.biancheng/net/cplus/");
}

main.cpp文件内容:

cpp 复制代码
#include <iostream>
#include "myfun.h"
using namespace std;
int main(){
   display();
   return 0;
}

可见主程序mian.cpp文件是用 C++ 编写的,而 display() 函数的定义myfun.c文件是用 C 编写的。

表面上看这个项目很完整,但调用 GCC 编译器运行此项目(见利用GCC编译器编译C/C++程序),提示错误信息如下:

cpp 复制代码
In function `main': undefined reference to `display()'

它表示编译器无法找到 main.cpp 文件中 display() 函数的实现代码。

导致此错误的原因,是 C++ 和 C 编译程序时,对函数名的处理方式不同。

(1)通过函数重载详解可知,C++ 之所以支持函数的重载,是因为在程序的编译阶段,C++会对函数的函数名进行"重命名",比如:

  • void Swap(int a, int b) 会被重命名为_Swap_int_int
  • void Swap(float x, float y) 会被重命名为_Swap_float_float

(2)但是C 语言不支持函数重载,它不会在编译阶段对函数的名称做较大的改动,比如:

  • void Swap(int a, int b) 会被重命名为_Swap;
  • void Swap(float x, float y)也会被重命名为_Swap。

不同的编译器有不同的重命名方式,但根据 C++ 标准编译后的函数名几乎都由原有函数名和各个参数的数据类型构成,而根据 C 语言标准编译后的函数名则仅由原函数名构成。

(3)这就意味着,使用 C 和 C++ 进行混合编程时,两者对函数名的处理方式不同,势必会造成编译器在程序链接阶段无法找到函数具体的实现,导致链接失败。

(4)幸运的是,C++ 给出了相应的解决方案,即借助 extern "C",就可以轻松解决 C++ 和 C 在处理代码方式上的差异性。

二、extern "C"详解

extern 是C和C++的一个关键字,但我们可以将 extern "C" 看做一个整体,和 extern 毫无关系。

extern "C" 既可以修饰一句 C++ 代码,也可以修饰一段 C++ 代码。

它的功能是让编译器以处理 C 语言代码的方式,来处理它所修饰的 C++ 代码。

仍以上面的例子进行说明。main.cpp 和 myfun.c 文件中都包含 myfun.h 头文件,当程序进行预处理操作时,myfun.h 头文件中的内容会被分别复制到这 2 个源文件中。对于 main.cpp 文件中包含的 display() 函数来说,编译器会以 C++ 代码的编译方式来处理它;而对于 myfun.c 文件中的 display() 函数来说,编译器会以 C 语言代码的编译方式来处理它。

为了避免 display() 函数以不同的编译方式处理,我们应该使其在 main.cpp 文件中仍以 C 语言代码的方式处理,这样就可以解决函数名不一致的问题。因此,可以像如下这样来修改 myfun.h:

cpp 复制代码
#ifdef __cplusplus
    extern "C" void display();
#else
    void display();
#endif

可以看到,当 myfun.h 被引入到 C++ 程序中时,会选择带有 extern "C" 修饰的 display() 函数;反之如果 myfun.h 被引入到 C 语言程序中,则会选择不带 extern "C" 修饰的 display() 函数。由此,无论 display() 函数位于 C++ 程序还是 C 语言程序,都保证了 display() 函数可以按照 C 语言的标准来处理。

再次运行该项目,会发现之前的问题消失了,可以正常运行:

cpp 复制代码
C++:http://c.biancheng/net/cplus/

在实际开发中,对于解决 C++ 和 C 混合编程的问题,通常在头文件中使用如下格式:

cpp 复制代码
#ifdef __cplusplus
    extern "C" {
#endif

void display();

#ifdef __cplusplus
    }
#endif

由此可以看出,extern "C" 大致有 2 种用法,当仅修饰一句 C++ 代码时,直接将其添加到该函数代码的开头即可;如果用于修饰一段 C++ 代码,只需为 extern "C" 添加一对大括号{},并将要修饰的代码囊括到括号内即可。

相关推荐
敲上瘾13 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
不会写代码的ys19 分钟前
【类与对象】--对象之舞,类之华章,共绘C++之美
c++
兵哥工控21 分钟前
MFC工控项目实例三十二模拟量校正值添加修改删除
c++·mfc
长弓聊编程31 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.38 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
暮色_年华1 小时前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
重生之我是数学王子1 小时前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
我们的五年1 小时前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
做人不要太理性2 小时前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.2 小时前
2、桥接模式
c++·桥接模式