《More Effective C++》《杂项讨论——34、如何在同一个程序中结合C++和C》

文章目录

  • 1、Terms34:如何在同一个程序中结合C++和C
    • 1.1 名称重整
    • 1.2 statics的初始化
    • 1.3 动态内存的分配
    • 1.4 数据结构的兼容性
  • 2、总结
  • 3、参考

1、Terms34:如何在同一个程序中结合C++和C

在大型项目中一般都用C++进行开发,但是不可避免会用一些C语言进行底层的调用。在确定C++和C的编译器都能产生兼容的目标文件之后你要重点考虑四件事情:名称重整,statics的初始化,动态内存的分配,数据结构的兼容性。

1.1 名称重整

一、问题引出
在 C++ 出现之前,很多实用的功能都是用 C 语言开发的,很多底层的库也是用 C 语言编写的,如果在 C++ 代码中可以兼容 C 语言代码,无疑能极大地提高 C++ 程序员的开发效率。但是在一个项目中,能否既包含 C++ 程序又包含 C 程序呢?
答案是可以的,但是要小心处理,因为C++ 和 C 在程序的编译、链接等方面都存在一定的差异,这些差异往往会导致程序运行失败。
C++编译器会为程序中的每个函数编出独一无二的名称;但是在C语言中没有必要,因为C语言不支持函数重载。但是发展到现在,C++项目中几乎都会有一些函数拥有相同的名称,但是重载并不兼容大部分链接器,因此名称重整(修饰),是对链接器的一个让步。
例如你有一个函数funca(),被编译器重整为xyzzy,你可以使用funca()的名称,没人在乎编译器重整的名称。但是如果funca处于C函数库中,你的C++原始代码会有一个头文件,有如下声明。

void funca(int x1,int x2,int x3,int x4);
//调用未经重整的函数名称

当你使用funca(a,b,c,d)时,你的目标文件内含的是这样的代码:

xyzzy(a,b,c,d)
//调用重整后的函数名称

但是如果funca()是一个C函数,那么funca()代码在目标文件会有一个名为funca的函数,名称并未重整。当你试图链接那个目标文件,就会获得一个错误信息,因为链接器寻找xyzzy的函数,并不存在。
解决方法就是告诉C++编译器,不要重整某些函数名称。如果调用一个名为funca的C函数,它的真正名称就叫做funca,你的目标代码内含有一份reference,指向那个名称,而非一个重整后的名称。

举个例子:
比如下面是一个用 C++ 和 C 混合编程实现的实例项目:
myfun.h文件内容:

void display();

myfun.c文件内容:

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

main.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++程序),提示错误信息如下:

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:

#ifdef __cplusplusextern "C" void display();
#elsevoid display();
#endif

可以看到,当 myfun.h 被引入到 C++ 程序中时,会选择带有 extern “C” 修饰的 display() 函数;反之如果 myfun.h 被引入到 C 语言程序中,则会选择不带 extern “C” 修饰的 display() 函数。由此,无论 display() 函数位于 C++ 程序还是 C 语言程序,都保证了 display() 函数可以按照 C 语言的标准来处理。
再次运行该项目,会发现之前的问题消失了,可以正常运行:

C++:http://c.biancheng/net/cplus/

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

#ifdef __cplusplusextern "C" {
#endifvoid display();#ifdef __cplusplus}
#endif

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

1.2 statics的初始化

许多代码会在main之前和之后执行代码。更明确说,static class对象、全局对象、namespace内对象以及文件范围(file scope)内的对象,其constructors总是在main之前执行,这个过程称为static initialization。通过static initialization产生出来的对象,其destructors必须在所谓的static destruction过程中被调用。那是发生在main结束之后。
经过编译的main,看起来像这样:

int main()
{performStaticInitialization();	//此行由编译器加入the statements you put in main go here;performStaticDestruction();		//此行由编译器加入
}

重点是:如果一个C++编译器采用这种方法来构造和析构对象,那么除非程序中有main,否则这种对象既不会被构造也不会被析构。
有时候,在C成分中撰写main似乎比较合理——如果程序主要以C完成而C++只是个支持库的话。尽管如此,C++程序库中内含static对象仍是极有可能的,所以如果能够,还是尽量在C++中撰写main的好。然而这并非意味你需要重写你的C代码。只要将你的C main重新命名的realMain,然后让C++ main调用realMain:

extern "C"
int realMain(int argc,char* argv[]); //以C语言完成此函数int main(int argc,char* argv[])
{realMain(argc,argv);
}

1.3 动态内存的分配

动态分配规则很简单:程序的C++部分使用new和delete,程序的C部分则使用malloc和free。
其次,严密地将new/delete与malloc/free分隔开来。
有时候说比做容易很多,考虑粗糙(但好用)的strdup函数,它虽然并非C或C++标准的一份子,却被广泛使用:

char* strdup(const char* ps); //返回一个ps所指字符串的副本

strdup分配的内存必须由strdup的调用者负责释放。如果它自C函数库,使用free;如果它来自一个C++程序库,那么应该用delete。因此调用strdup后,你应该做的事情不只随系统的不同而不同,也随编译器的不用而不同。为了降低这种头痛的移植问题,请避免调用标准程序库以外的函数或是大部分计算平台上尚未稳定的函数。

1.4 数据结构的兼容性

如果你的C++和C编译器有着兼容的输出,两个语言的函数便可以安全的交换对象指针、non-member函数指针或者static函数指针。很自然的,structs以及内建类型的变量也可以安全跨越C++/C边界。
对于struct来说没如果只是加上一些非虚函数,其内存布局应该不会改变,如果加上虚函数,或者继承也会改变struct的布局,所以一个struct如果带有base structs(或classes),无法和C函数交换。

2、总结

  • 确定你的C++和C编译器产出兼容的目标文件(object files) 。
  • '将双方都使用的函数声明为extern “C”。
  • 如果可能,尽量在C++中撰写main。
  • 总是以delete删除new返回的内存;总是以free释放malloc返回的内存。
  • 将两个语言间的“数据结构传递”限制于C所能了解的形式;
  • C++ structs如果内含非虚函数,倒是不受此限制。

3、参考

3.1 《More Effective C++》
3.2 如何在同一个程序中结合C++和C

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/40345.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【宠粉赠书】UML 2.5基础、建模与设计实践

为了回馈粉丝们的厚爱&#xff0c;今天小智给大家送上一套系统建模学习的必备书籍——《UML 2.5基础、建模与设计实践》。下面我会详细给大家介绍这本书&#xff0c;文末留有领取方式。 图书介绍 《UML 2.5基础、建模与设计实践》以实战为主旨&#xff0c;结合draw.io免费软件…

匿名内部类

下面代码中&#xff0c;Person24 是一个抽象类&#xff0c;这意味着它不能被直接实例化&#xff0c;只能通过继承它的子类来实现其抽象方法。代码片段中展示了如何使用匿名内部类来实现一个抽象类的实例。 package chapter04;public class Java24_Object_匿名内部类 {public s…

verilog行为建模(三):块语句

目录 1.块语句2.延迟赋值语句 微信公众号获取更多FPGA相关源码&#xff1a; 1.块语句 块语句用来将多个语句组织在一起&#xff0c;使得他们在语法上如同一个语句。 块语句分为两类&#xff1a; 顺序块&#xff1a;语句置于关键字begin和end之间&#xff0c;块中的语句以顺…

鸿蒙‘ohpm‘ 不是内部或外部命令,也不是可运行的程序-解决方案

&#x1f525; 博客主页&#xff1a; 小韩本韩&#xff01; ❤️ 感谢大家点赞&#x1f44d;收藏⭐评论✍️ 在鸿蒙的DevEco Studio的终端下输入 onpm -v 或者 你需要下载第三方ohpm包的时候提示‘ohpm‘ 不是内部或外部命令&#xff0c;也不是可运行的程序- 主要是因为我们…

学习测试1

计算机基础 1、计算机范式&#xff1a;冯诺依曼机 2、存储单元 bit、byte、KB、MB、GB3、网络 ip、域名、ping 域名、 ipconfig测试工作的流程 ------------------------------------------------------------------------------------------- 一 编写测试大纲 罗列测试…

C++STL函数对象的应用

STL函数对象 文章目录 STL函数对象1.基本概念2.使用方法1. 简单函数对象示例2. 函数对象作为算法参数3. Lambda表达式作为函数对象 2.一元谓词和二元谓词1.一元谓词2.二元谓词3.总结 3.算术仿函数1.使用示例2.Lambda表达式的替代 4.关系仿函数5.逻辑仿函数 C中的函数对象&#…

文化创新与社交媒体:探索Facebook的足迹

在过去的十多年里&#xff0c;Facebook从一个简单的校园社交网络发展成为全球最大的社交媒体平台之一。它不仅改变了人们的沟通方式&#xff0c;更在许多方面推动了文化的创新和变革。本文将深入探索Facebook如何通过其平台的演进和功能创新&#xff0c;成为文化创新的重要推动…

Ubuntu / Debian安装FTP服务

本章教程,记录在Ubuntu中安装FTP服务的具体步骤。FTP默认端口:21 1、安装 pure-ftpd sudo apt-get install pure-ftpd2、修改默认配置 # 与 centos 不同,这里需要在 /etc/pure-ftpd/conf 文件夹下执行下列命令,增加对应配置文件: # 创建 /etc/pure-ftpd/conf/PureDB 文件…

【数据结构】(6.2)堆的应用——Top-K问题(C语言)

系列文章目录 文章目录 系列文章目录问题引入一、TopK 问题 是什么&#xff1f;二、TopK 问题解决思路2.1 TopK 思路2.2 随机产生数字2.2 完整代码2.3 验证结果 问题引入 TopK 问题 (在一堆数据里面找到前 K 个最大 / 最小的数)。 一、TopK 问题 是什么&#xff1f; 生活中也…

2024 最新docker仓库镜像,6月,7月

目前下面的docker仓库镜像源还能使用。 vi /etc/docker/daemon.json添加如下配置{"registry-mirrors": ["https://hub.uuuadc.top", "https://docker.anyhub.us.kg", "https://dockerhub.jobcher.com", "https://dockerhub.icu&…

船舶雷达与导航系统选择7/8防水插座的原因分析

概述 船舶雷达与导航系统在现代航海中扮演着至关重要的角色&#xff0c;它们为船舶提供准确的导航信息&#xff0c;确保航行的安全和效率。在这些系统中&#xff0c;7/8防水插座的使用尤为重要&#xff0c;因为它们能够在恶劣的海上环境中提供稳定的电力和信号连接。接下来&am…

python的os.walk()

os.walk() 是一个非常有用的函数&#xff0c;用于在Python中遍历文件夹树。它返回一个生成器&#xff0c;该生成器在每次迭代时返回一个包含三个元素的元组&#xff1a;(当前文件夹的路径&#xff0c;文件夹中的子文件夹的列表&#xff0c;文件夹中的文件的列表)。这个函数对于…

左耳听风_007_06_如何才能拥有技术领导力

你好&#xff0c;我是陈浩老明左耳朵house.那通过上节课呢&#xff0c;相信你现在已经理解了什么才是技术领导力。 那今天呢我就来跟你继续聊一聊怎样才能拥有技术领导力。 首先呢你需要吃透基础技术。 因为基础技术啊是各种上层技术共同的技术。 吃透基础技术是为了更好的…

Outlook发送大文件的问题是什么?怎么解决?

Outlook不仅是一款电子邮件客户端&#xff0c;还包括日历、任务、笔记、联系人等功能&#xff0c;同时与Microsoft Office套件中的其他应用程序&#xff08;如Word、Excel、PowerPoint等&#xff09;集成紧密&#xff0c;方便用户在不同应用程序之间切换&#xff0c;提高工作效…

LLM - 神经网络的组成

1. 一个神经元的结构&#xff1a;即接受多个输入X向量&#xff0c;在一个权重向量W和一个偏执标量b的作用下&#xff0c;经过激活函数后&#xff0c;产生一个输出。 2. 一层神经网络的结构&#xff1a;该层网络里的每个神经元并行计算&#xff0c;得到各自的输出;计算方式是输入…

「植物大战僵尸杂交版」保姆级攻略大全以及下载指南

植物大战僵尸杂交版自推出以来&#xff0c;以其独特的植物组合和策略玩法&#xff0c;迅速赢得了玩家们的喜爱。如果你正准备加入这场植物与僵尸的战斗&#xff0c;或者已经在战斗中寻求突破&#xff0c;那么这份保姆级的攻略大全将是你的得力助手。同时&#xff0c;我们也提供…

Mysql——数据库约束和加简单查询

数据库中的约束 在创建表格的过程中可以给某些字段追加约束条件 非空约束 NOT NULL NK create table t_user ( id int(3) not null, username varchar(10), password varchar(15) ); 唯一约束 UNIQUE UK create table t_user ( id int(3) not null, username varch…

[笔记] 高等数学在各工程门类的典型应用场景

1.应用场景 1.微积分似乎是在解算椭圆方程中引入的&#xff1f;但是这个数学工具第一次应用于现实的工程问题是什么时候&#xff1f;什么场景&#xff1f;什么问题&#xff1f; 微积分的发展确实与椭圆方程有关&#xff0c;但它最初的应用场景远不止于此。 微积分首次被应用…

C++期末模拟

id:124 A. 一、会员积分&#xff08;期末模拟&#xff09; 题目描述 某电商网站的会员分为&#xff1a;普通、贵宾两个级别 普通会员类Member&#xff0c;包含编号、姓名、积分三个属性&#xff0c;编号和积分是整数&#xff0c;姓名是字符串 操作包括构造、打印、积分累加、…

【JavaWeb程序设计】Web基础-JavaScript

目录 一、函数与事件的使用 1. 编写一个html页面&#xff0c;使用Javascript完成数字的平方计算。 1.1 运行截图 1.2 JS代码 1.3 HTML代码 2. 要求文本框中只能输入字母 2.1 运行截图 2.2 下载jquery-3.4.1并引用 2.3 JS代码 2.4 HTML代码 3. 在文本框分别输入两个…