关于 extern C的说明

在用C++的项目源码中,经常会不可避免的会看到下面的代码

1 #ifdef __cplusplus
2 extern "C" {
3 #endif
4  
5     /*...*/
6  
7 #ifdef __cplusplus
8 }
9 #endif

它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试or笔试中。下面我就从以下几个方面来介绍它:

———— 目录 ————

1、#ifdef _cplusplus/#endif _cplusplus及发散
2、extern "C"
  2.1、extern关键字
  2.2、"C"
  2.3、小结extern "C"
3、C和C++互相调用
  3.1、C++的编译和连接
  3.2、C的编译和连接
  3.3、C++中调用C的代码
  3.4、C中调用C++的代码
4、C和C++混合调用特别之处函数指针

 —————————

1、#ifdef _cplusplus/#endif _cplusplus及发散

在介绍extern "C"之前,我们来看下#ifdef _cplusplus/#endif _cplusplus的作用。很明显#ifdef/#endif、#ifndef/#endif用于条件编译,#ifdef _cplusplus/#endif _cplusplus——表示如果定义了宏_cplusplus,就执行#ifdef/#endif之间的语句,否则就不执行。

在这里为什么需要#ifdef _cplusplus/#endif _cplusplus呢?因为C语言中不支持extern "C"声明,如果你明白extern "C"的作用就知道在C中也没有必要这样做,这就是条件编译的作用!在.c文件中包含了extern "C"时会出现编译时错误。

既然说到了条件编译,我就介绍它的一个重要应用——避免重复包含头文件。还记得腾讯笔试就考过这个题目,给出类似下面的代码(下面是我最近在研究的一个开源web服务器——Mongoose的头文件mongoose.h中的一段代码):

 1 #ifndef   MONGOOSE_HEADER_INCLUDED
 2 #define  MONGOOSE_HEADER_INCLUDED
 3  
 4 #ifdef __cplusplus
 5 extern "C" {
 6 #endif /* __cplusplus */
 7  
 8     /*.................................
 9      * do something here
10      *.................................
11      */
12  
13 #ifdef __cplusplus
14 }
15 #endif  /* __cplusplus */
16  
17 #endif  /* MONGOOSE_HEADER_INCLUDED */

然后叫你说明上面宏#ifndef/#endif的作用?为了解释一个问题,我们先来看两个事实:

  • 这个头文件mongoose.h可能在项目中被多个源文件包含(#include "mongoose.h"),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中mongoose.h可能会被#include两次(如,a.h头文件包含了mongoose.h,而在b.c文件中#include a.h和mongoose.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
  • 从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立

为了解决这个问题,上面代码中的

1 #ifndef MONGOOSE_HEADER_INCLUDED 
2 #define    MONGOOSE_HEADER_INCLUDED 
3          /*……………………………*/ 
4 #endif /* MONGOOSE_HEADER_INCLUDED */    

就起作用了。如果定义了MONGOOSE_HEADER_INCLUDED,#ifndef/#endif之间的内容就被忽略掉。因此,编译时第一次看到mongoose.h头文件,它的内容会被读取且给定MONGOOSE_HEADER_INCLUDED一个值。之后再次看到mongoose.h头文件时,MONGOOSE_HEADER_INCLUDED就已经定义了,mongoose.h的内容就不会再次被读取了。

2、extern "C"

首先从字面上分析extern "C",它由两部分组成——extern关键字、"C"。下面我就从这两个方面来解读extern "C"的含义。

2.1、extern关键字

在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。首先来一个例子:

1 //file1.c:
2     int x=1;
3     int f(){do something here}
4 
5 //file2.c:
6     extern int x;
7     int f();
8     void g(){x=f();}

在file2.c中g()使用的x和f()是定义在file1.c中的。extern关键字表明file2.c中x,仅仅是一个变量的声明,其并不是在定义变量x,并未为x分配内存空间。变量x在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。但是可以声明多次,且声明必须保证类型一致,如:

 1 //file1.c:
 2     int x=1;
 3     int b=1;
 4     extern c;
 5 
 6 //file2.c:
 7     int x;// x equals to default of int type 0
 8     int f();
 9     extern double b;
10     extern int c;

在这段代码中存在着这样的三个错误:

  1. x被定义了两次
  2. b两次被声明为不同的类型
  3. c被声明了两次,但却没有定义

回到extern关键字,extern是C/C++语言中表明函数全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

与extern对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

2.2、"C"

典型的,一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。不同语言编写的代码互相调用是困难的,甚至是同一种编写的代码但不同的编译器编译的代码。例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一样。

为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。例如,声明C和C++标准库函数strcyp(),并指定它应该根据C的编译和连接规约来链接:

1 extern "C" char* strcpy(char*,const char*);

注意它与下面的声明的不同之处:

extern char* strcpy(char*,const char*);

下面的这个声明仅表示在连接的时候调用strcpy()。

extern "C"指令非常有用,因为C和C++的近亲关系。注意:extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。

还有要说明的是,extern "C"指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern "C",仍然要遵守C++的类型检测、参数转换规则。

再看下面的一个例子,为了声明一个变量而不是定义一个变量,你必须在声明时指定extern关键字,但是当你又加上了"C",它不会改变语义,但是会改变它的编译和连接方式。

如果你有很多语言要加上extern "C",你可以将它们放到extern "C"{ }中。

2.3、小结extern "C"

通过上面两节的分析,我们知道extern "C"的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern "C",表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)

3、C和C++互相调用

我们既然知道extern "C"是实现的类C和C++的混合编程。下面我们就分别介绍如何在C++中调用C的代码、C中调用C++的代码。首先要明白C和C++互相调用,你得知道它们之间的编译和连接差异,及如何利用extern "C"来实现相互调用。

3.1、C++的编译和连接

C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:

1 void print(int i);
2 void print(char c);
3 void print(float f);
4 void print(char* s);

编译为:

1  _print_int
2  _print_char
3  _print_float
4 _pirnt_string

这样的函数名,来唯一标识每个函数。注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找_print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。

C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。

3.2、C的编译和连接

C语言中并没有重载和类这些特性,故并不像C++那样print(int i),会被编译为_print_int,而是直接编译为_print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的print(3)时,它会去找_print_int(3)。因此extern "C"的作用就体现出来了。

3.3、C++中调用C的代码

假设一个C的头文件cHeader.h中包含一个函数print(int i),为了在C++中能够调用它,必须要加上extern关键字(原因在extern关键字那节已经介绍)。cHeader.h文件的代码如下:

1 #ifndef C_HEADER
2 #define C_HEADER 
3 
4     extern void print(int i);
5 
6 #endif C_HEADER

相对应的实现文件为cHeader.c的代码为:

1 #include <stdio.h>
2 #include "cHeader.h"
3 void print(int i)
4 {
5     printf("cHeader %d\n",i);
6 }

现在C++的代码文件main.cpp(当然你也可以起名为其他的xxx.cpp文件名,只是想表达的是cpp文件去调用c文件里面的函数.) 中引用C中的print(int i)函数:

 1 #include "stdafx.h"
 2 #include<iostream>
 3 //#include<iomanip>
 4 //#include <stdio.h>
 5 //#include <malloc.h>
 6 //using namespace std;
 7 
 8 extern "C"{
 9     #include "cHeader.h"
10 }
11 
12 int main()
13 {
14     print(3);
15     system("pause");
16     return 0;
17 }

我的工程建好后是如下情况:

运行的时候会报错:

解决方法如下:

 

然后程序就可以正常编译运行了,程序运行结果如下:

3.4、C中调用C++的代码

现在换成在C中调用C++的代码,这与在C++中调用C的代码有所不同。我们新建一个win32控制台工程,名字为c_diao_cpp,

在cppHeader.h头文件中定义了下面的代码:

1 ifndef CPP_HEADER
2 #define CPP_HEADER
3 
4 extern "C" void print(int i);
5  
6 #endif CPP_HEADER

相应的实现文件cppHeader.cpp文件中代码如下:

1 #include "cppHeader.h"
2 #include <iostream>
3 using namespace std;
4 
5 void print(int i)
6 {
7     cout<<"cppHeader "<<i<<endl;
8 }

在c_diao_cpp.c 代码文件中调用print函数:

1 #include "stdafx.h"
2 extern void print(int i);
3 
4 int _tmain(int argc, _TCHAR* argv[])
5 {
6     print(3);
7     return 0;
8 }

工程目录结构如下:

运行结果如下:

注意在C的代码文件中直接#include "cppHeader.h"头文件,编译出错。而且如果不加extern int print(int i)编译也会出错。

4、C和C++混合调用特别之处函数指针

当我们C和C++混合编程时,有时候会用一种语言定义函数指针,而在应用中将函数指针指向另一中语言定义的函数。如果C和C++共享同一中编译和连接、函数调用机制,这样做是可以的。然而,这样的通用机制,通常不然假定它存在,因此我们必须小心地确保函数以期望的方式调用。

而且当指定一个函数指针的编译和连接方式时,函数的所有类型,包括函数名、函数引入的变量也按照指定的方式编译和连接。如下例:

typedef int (*FT) (const void* ,const void*);//style of C++extern "C"{typedef int (*CFT) (const void*,const void*);//style of Cvoid qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
}void isort(void* p,size_t n,size_t sz,FT cmp);//style of C++
void xsort(void* p,size_t n,size_t sz,CFT cmp);//style of C//style of C
extern "C" void ysort(void* p,size_t n,size_t sz,FT cmp);int compare(const void*,const void*);//style of C++
extern "C" ccomp(const void*,const void*);//style of Cvoid f(char* v,int sz)
{//error,as qsort is style of C//but compare is style of C++qsort(v,sz,1,&compare);qsort(v,sz,1,&ccomp);//ok
     isort(v,sz,1,&compare);//ok//error,as isort is style of C++//but ccomp is style of Cisort(v,sz,1,&ccopm);
}

注意:typedef int (*FT) (const void* ,const void*),表示定义了一个函数指针的别名FT,这种函数指针指向的函数有这样的特征:返回值为int型、有两个参数,参数类型可以为任意类型的指针(因为为void*)。

最典型的函数指针的别名的例子是,信号处理函数signal,它的定义如下:

1 typedef void (*HANDLER)(int);
2 HANDLER signal(int ,HANDLER);

上面的代码定义了信函处理函数signal,它的返回值类型为HANDLER,有两个参数分别为int、HANDLER。 这样避免了要这样定义signal函数:

1 void (*signal (int ,void(*)(int) ))(int)

比较之后可以明显的体会到typedef的好处。

这篇文章主要参考了:http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html 这篇博客

 

转载于:https://www.cnblogs.com/www-caiyin-com/p/6757916.html

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

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

相关文章

Nginx 面试 40 问

Nginx是一款轻量级的Web服务器、反向代理服务器&#xff0c;由于它的内存占用少&#xff0c;启动极快&#xff0c;高并发能力强&#xff0c;在互联网项目中广泛应用。 那么关于 Nginx 的核心技术点有哪些呢&#xff1f; 什么是Nginx&#xff1f; Nginx是一个 轻量级/高性能的…

用Cocos2dx开发棋牌游戏的观点解析

众所周知&#xff0c;目前棋牌游戏特别的火。很多游戏公司都想在这一块赚钱&#xff0c;可是却不知用什么软件比较好的去开发棋牌游戏&#xff0c;对此&#xff0c;我列出了两款比较靠谱的软件去开发棋牌游戏&#xff0c;希望对大家有帮助&#xff01; 第一款软件是cocos2dx,它…

我把《系统设计》系列整理成了 PDF

大家好&#xff0c;我是等天黑。相信很多朋友应该注意到了&#xff0c;我最近发了很多系统设计的文章。是的&#xff0c;到目前为止&#xff0c;已经发了有 7 篇文章。这些内容主要翻译自 Alex Xu 的 《System Design Interview》&#xff0c;有卷一和卷二两本。System Design …

高性能IO模型浅析

服务器端编程经常需要构造高性能的IO模型&#xff0c;常见的IO模型有四种&#xff1a; &#xff08;1&#xff09;同步阻塞IO&#xff08;Blocking IO&#xff09;&#xff1a;即传统的IO模型。 &#xff08;2&#xff09;同步非阻塞IO&#xff08;Non-blocking IO&#xff09;…

PHP个人博客项目------切切歆语博客

2019独角兽企业重金招聘Python工程师标准>>> phpmysqlapache, ThinkPHP3.2框架开发 我的个人博客项目 适合新手练习 源码地址下载&#xff1a;https://github.com/DickyQie/php-myblog 转载于:https://my.oschina.net/zhangqie/blog/1785867

IOS_SearchBar搜索栏及关键字高亮

搜索框的效果演示: 这个就是所谓的搜索框了,那么接下来我们看看如何使用代码来实现这个功能. 我所使用的数据是英雄联盟的英雄名单,是一个JSON数据的txt文件, JSON数据的处理代码如下所示: ?123456//获取文件的路径pathNSString *path [[NSBundle mainBundle] pathForResourc…

Java设计模式之(工厂模式)--简单工厂模式--工厂方法模式--抽象工厂模式

工厂模式&#xff1a; 工厂模式可以分为三类&#xff1a; 1&#xff09;简单工厂模式&#xff08;Simple Factory&#xff09; 2&#xff09;工厂方法模式&#xff08;Factory Method&#xff09; 3&#xff09;抽象工厂模式&#xff08;Abstract Factory&#xff09; 简单工…

今天很多 CTO 都是被干掉的,因为他没有成就业务

作者&#xff5c;乔新亮 编辑&#xff5c;邓艳琴 我可以丝毫不开玩笑地说&#xff0c;今天&#xff0c;很多传统企业里的研发都只是“工人”&#xff0c;哪怕是 CTO&#xff0c;充其量也只是“高级工人”&#xff0c;如果不转换思维去成就业务&#xff0c;就只能停留在工人级…

中航工业集团金网络(北京)电子商务有限公司副总经理刘正珩:航空“智”造的供应链支撑平台...

编者按 “十三五”时期是我国贸易发展的重要战略机遇期&#xff0c;物流产业发展迅速&#xff0c;智慧供应链已经成为推动流通大国向流通强国过程中的重要行动。6月2日&#xff0c;由上海市国有资产监督管理委员会、上海市邮政管理局、上海市商务委员会指导&#xff0c;上海市国…

创建、检查和反编译世界上(几乎)最短的 C# 程序

创建、检查和反编译世界上&#xff08;几乎&#xff09;最短的 C# 程序原文来自https://www.stevejgordon.co.uk/creating-inspecting-decompiling-the-worlds-smallest-csharp-program在这篇文章中&#xff0c;我认为创建世界上&#xff08;几乎&#xff09;最短的 C# 程序然后…

Linux下画原理图和PCB

Linux下画原理图和PCBWindows下大名鼎鼎的Allegro和经典的Protel 99SE都是不支持Linux操作系统的。做Linux驱动开发免不了要看一下原理图和PCB。一般的做法有三种&#xff1a; 1.主机使用Windows系统&#xff0c;将Linux装在VMWARE之类的虚拟机中这样能够使用Windows下的软件看…

配置中心 App Configuration (二):Feature Flag 功能开关特性

写在前面Web服务开发过程中我们经常有这样的需求&#xff1a;某些功能我必须我修改了配置才启用&#xff0c;比如新用户注册送券等&#xff1b;某个功能需到特定的时间才启用&#xff0c;过后就失效&#xff0c;比如春节活动等&#xff1b;某些功能&#xff0c;我想先对10%的用…

联想(Lenovo)小新310经典版进bios方法

1&#xff0c;找到novo按钮。 2&#xff0c;在关机的状态下桶一下小孔&#xff0c;不用任何操作&#xff0c;电脑进入bios选择界面。转载于:https://www.cnblogs.com/senior-engineer/p/6761457.html

C#中的匿名类型

这节来讲一下C#中的匿名类型。匿名类在C#中&#xff0c;我们可以不去显示的声明一个类&#xff0c;而是通过匿名类去临时声明一个类结构去帮助我们去完成一些功能。声明一个匿名类&#xff0c;我们可以像下面这样做&#xff1a;var Anonymousnew {name"charles",year…

MySQL之MHA高可用集群

目录 一、MHA概述 1.1.MHA 是什么 1.2.MHA 的组成 1.3.MHA 的特点 二、MHA搭建准备 2.1.实验思路 三、MHA搭建 3.1配置主从复制 3.2.安装 MHA 软件 3.3.故障模拟 3.4.故障修复 四、总结 一、MHA概述 1.1.MHA 是什么 1.MHA&#xff08;MasterHigh Availability&…

配置中心 App Configuration (一):轻松集成到Asp.Net Core

写在前面在日常开发中&#xff0c;我这边比较熟悉的配置中心有&#xff0c;携程Apollo&#xff0c;阿里Nacos(配置中心&#xff0c;服务治理一体)之前文章&#xff1a;Asp.Net Core与携程阿波罗(Apollo)的第一次亲密接触总体来说&#xff0c;Apollo和Nacos社区都比较活跃&#…

详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] good

目录 前言现象源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口的具体应用常用HandlerMethodArgumentResolver介绍常用HandlerMethodReturnValueHandler介绍本文开头现象…

对不起,我不是一个自律的人

大家好&#xff0c;我是 &#x1f41f;&#x1f4a8;。前天&#xff0c;星球 的一位大学生朋友问了我几个问题&#xff1a;你大学时如何安排每日的时间&#xff1f;为什么能学那么多技术&#xff1f;你会学习到很晚吗&#xff1f;你是如何保持自律的&#xff1f;我觉得这几个问…

保证接口数据安全的10种方案

前言 大家好&#xff0c;我是程序汪&#xff0c;互联网项目需要特别注意数据安全&#xff0c;如果你简历上是互联网类型项目&#xff0c;安全方面肯定要能说出个一二三&#xff0c;下面分享下这方面的干货&#xff0c;大家可以记住几条&#xff0c;面试时好说道说道 我们日常…

Html5本地存储LocalStorage

HTML5 提供了两种在客户端存储数据的新方法&#xff1a; localStorage - 没有时间限制的数据存储sessionStorage - 针对一个 session 的数据存储在浏览器中打开审查元素&#xff08;如谷歌F12&#xff09;&#xff0c;在Resources下面可以查看里面的数据。 localStorage提供了几…