当前位置: 首页 > 编程日记 > 正文

C++项目中的extern C {}

2010-07-10 19:45 by 吴秦, 92864 阅读, 22 评论, 收藏, 编辑

引言

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

?
1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*.................................
* do something here
*.................................
*/
#ifdef __cplusplus
}
#endif /* __cplusplus */
#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)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
  • 从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立

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

#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
/*……………………………*/
#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
2
3
4
5
6
7
//file1.c:
int x=1;
int f(){do something here}
//file2.c:
extern int x;
int f();
void g(){x=f();}

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

?
1
2
3
4
5
6
7
8
9
//file1.c:
int x=1;
int b=1;
extern c;
//file2.c:
int x;// x equals to default of int type 0
int f();
extern double b;
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*);

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

?
1
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
2
3
4
void print(int i);
void print(char c);
void print(float f);
void print(char* s);

编译为:

?
1
2
3
4
_print_int
_print_char
_print_float
_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关键字那节已经介绍)。它的代码如下:

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

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

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

现在C++的代码文件C++.cpp中引用C中的print(int i)函数:

?
1
2
3
4
5
6
7
8
9
extern "C"{
#include "cHeader.h"
}
int main(int argc,char** argv)
{
print(3);
return 0;
}

执行程序输出:

image

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

现在换成在C中调用C++的代码,这与在C++中调用C的代码有所不同。如下在cppHeader.h头文件中定义了下面的代码:

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

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

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

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

?
1
2
3
4
5
6
extern void print(int i);
int main(int argc,char** argv)
{
print(3);
return 0;
}

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef int (*FT) (const void* ,const void*);//style of C++
extern "C"{
typedef int (*CFT) (const void*,const void*);//style of C
void 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 C
void 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 C
isort(v,sz,1,&ccopm);
}

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

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

?
1
2
typedef void (*HANDLER)(int);
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,如需转载请自行联系原作者

相关文章:

C# 自定义事件和委托

http://www.cnblogs.com/huomm/archive/2007/12/04/982869.html 转载于:https://www.cnblogs.com/xxvv/archive/2012/09/22/3648589.html

参加UI培训有发展吗?

UI设计岗在最近的很多招聘网上都越来越多&#xff0c;逐渐引起了很多人的关注&#xff0c;大家都想知道转行参加UI培训有发展吗?本篇文章就和大家分享一下。 参加UI培训有发展吗? 先来看看UI设计的现状&#xff0c;UI设计行业这几年发展非常快&#xff0c;目前已经初具规模&a…

lampp开机启动

安装好之后&#xff0c;输入以下命令即可&#xff1a; ln -s /opt/lampp/lampp /etc/rc.d/rc3.d/S99lampp ln -s /opt/lampp/lampp /etc/rc.d/rc4.d/S99lampp ln -s /opt/lampp/lampp /etc/rc.d/rc5.d/S99lampp转载于:https://www.cnblogs.com/imkun/archive/2012/09/23/269929…

Properties类读写配置文件

Properties类读写简单配置文件相当的方便&#xff0c;以前竟然没注意到这个类。 FileInputStream fs new FileInputStream("config.ini"); Properties pros new Properties(); pros.load(fs); pros.setProperty("test", &…

如何在Jupyter Lab中显示pyecharts的图形?

这篇图文是《如何利用pyecharts绘制酷炫的桑基图&#xff1f;》的补充。 在这篇图文中给出的代码是使用pycharm调试的&#xff0c;而自己分享的时候&#xff0c;是使用Jupter Lab。 如果沿用这篇文章的代码&#xff0c;渲染后的图形仍然需要到sankey.html文件中查看&#xff…

参加web前端培训要学会哪些技能

想要成为一名合格的web前端程序猿&#xff0c;要学习的东西有很多&#xff0c;那么参加web前端培训要学会哪些技能呢?来看看下面的详细介绍就知道了。 参加web前端培训要学会哪些技能?想从事web前端开发&#xff0c;只会HTML、CSS和JavaScript这三个要素是远远不够的。身为一…

微软的研发思路发生改变了 Visual Studio 2012 Update 1抢先看

随着微软上周正式发布Visual Studio 2012&#xff0c;微软公司随即宣布了他们的下一版本。Update 1的CTP&#xff08;社区技术预览&#xff09;版预计本月底可用&#xff0c;而Update 1的最终版应该2012年底可用。微软曾表示&#xff0c;他们希望在VS2012上实现的若干目标之一是…

json的序列化与反序列化

json 是一种轻量级的数据交换格式&#xff0c;也是完全独立于任何程序语言的文本格式。 本文介绍json字符串的序列化与反序列化问题。 序列化 是指将变量&#xff08;对象&#xff09;从内存中变成可存储或可传输的过程。反序列化 是指将变量内容从序列化的对象重新读到内存里…

socket 995 错误 boost

这个错误的中文解释是&#xff1a;由于线程退出或应用程序请求&#xff0c;已中止 I/O 操作。 最近几天学习boost asio 在抄官方的一个实例代码时遇到 了&#xff0c;这个错误搞了我三天才解决&#xff0c;就是在一行代码中少了一个 s 所致。 正确的代码是这样 的 boost::asio:…

几何图形在logo设计中的有哪些情感意义?

作为一名合格的UI设计师&#xff0c;工作中遇到的问题有很多&#xff0c;例如logo设计&#xff0c;是很多UI设计师的一个日常工作之一&#xff0c;小编今天为大家介绍的UI设计培训教程就是几何图形在logo设计中的有哪些情感意义?希望能够帮助到大家。 UI设计培训教程&#xff…

Linux自学笔记——haproxy

HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理&#xff0c;支持虚拟主机&#xff0c;它是免费、快速并且可靠地一套解决方案。HAProxy特别适用于那些负载特大的web站点&#xff0c;这些站点通常又需要会话保持或七层处理。HAProxy运行在时下的硬件上&#xff0c;完…

如何利用pyecharts绘制炫酷的关系网络图?

如何利用pyecharts绘制炫酷的关系网络图 这是本学期在大数据哲学与社会科学实验室做的第六次分享了。 第一次分享的是&#xff1a; 如何利用“wordcloudjieba”制作中文词云&#xff1f; 第二次分享的是&#xff1a; 如何爬取知乎中问题的回答以及评论的数据&#xff1f; …

设Excle的cell中显示一个下拉列表选择框

描述&#xff1a;我想让excle的sheet页中的B列的每个cell输入内容的时候将A列所有行的内容作为下拉列表的选择内容 效果如下&#xff1a; 实现的步骤如下&#xff1a; 1.点击B列的头&#xff0c;选中整列 2.点击菜单栏的“数据”-》“数据有效性”&#xff0c;弹出如下的对话框…

Python培训中有哪些是必须学的运算符

不管是学Python技术还是其他的编程技术&#xff0c;运算符的学习都是少不了的&#xff0c;本期小编为大家推荐的教程就是关于Python培训中有哪些是必须学的运算符?来看看下面的详细介绍内容。 Python培训中有哪些是必须学的运算符? 布尔值&#xff1a;值只有两个&#xff0c;…

centos6 搭建heartbeat

Heartbeat是High-Availability Linux Project (Linux下的高可用性项目)的产物&#xff0c;是一套提供防止业务主机因不可避免的意外性或计划性宕机问题的高可用性软件。Heartbeat可以从Linux-HA 项目Web 站点免费获得&#xff0c;它提供了所有HA &#xff08;高可用性&#xff…

标准C++中的string类的用法总结

相信使用过MFC编程的朋友对CString这个类的印象应该非常深刻吧&#xff1f;的确&#xff0c;MFC中的CString类使用起来真的非常的方便好用。但是如果离开了MFC框架&#xff0c;还有没有这样使用起来非常方便的类呢&#xff1f;答案是肯定的。也许有人会说&#xff0c;即使不用M…

NCEPU:线下组队学习周报(007)

线下组队学习 经过一段时间的准备&#xff0c;我们组织的线下组队学习逐步进入正轨。欢迎华北电力大学保定校区的伙伴加入进来大家一起学习一起成长。 我们开展组队学习的内容为&#xff1a; &#xff08;1&#xff09;周志华的《机器学习》&#xff08;西瓜书&#xff09; …

java培训面试技巧分享

很多人在学会java技术之后&#xff0c;就开始筹备自己的面试了&#xff0c;java技术在互联网行业的需求是很大的&#xff0c;所以内卷是很严重的&#xff0c;在面试环节一定要全力以赴才行&#xff0c;下面小编就教大家一些java培训面试技巧&#xff0c;希望能帮助到大家。 jav…

Python线程锁

多线程适用于IO密集型&#xff0c;多线程实现方式有两种&#xff0c;详见下方例子 例子&#xff1a; import threading class MyThread(threading.Thread): def __init__(self, args): #使用super写法&#xff0c;按照父类.方法的方式直接重写 super(MyThread, self).__init__(…

session_id

<?php session_start(); echo session_id(); // 输出 dqr58dnuqj2gufvg4o3tmjb9v4?>设置 session_id()<?php session_id("NowaMagic"); session_start(); echo session_id(); ?>程序恢复session&#xff0c;首先要知道session_id&#xff0c;大家通过…

【青少年编程】【二级】寻找宝石

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

零基础参加java培训主要学什么

互联网行业永远是一个需求非常大的行业&#xff0c;尤其是技术人员岗位&#xff0c;java技术岗一直很受大家的关注&#xff0c;不少学员都是零基础开始学&#xff0c;那么零基础参加java培训主要学什么呢?来看看下面的详细介绍。 零基础参加java培训主要学什么?其实无论学什么…

python pexpect

123456789101112131415161718192021222324252627282930313233343536373839404142434445Python 远程批量修改密码脚本#tar -zxvf pexpect-3.0.tar.gz#cd pexpect-3.0#python setup.py install#!/usr/bin/env python#coding:utf8import pexpect impo…

牧小熊:Adobe Illustrator 在科研作图中的应用!

聂雄伟&#xff0c;华中农业大学&#xff0c;Datawhale原创作者。 关于大数据的完整讲解&#xff01;电商数据分析项目总结&#xff01;数据项目总结&#xff1a;蛋壳公寓租金分析&#xff01;数据项目总结&#xff1a;王者荣耀总决赛预测&#xff01;你要的用户画像实践来了&…

win7,windowsXP安装mysql-5.1.49-win32,中文版、英文版,通吃

安装过程与其它Windows安装程序一样&#xff0c;首先出现的是安装向导欢迎界面[img]file:///C:/Users/房继诺/AppData/Roaming/Tencent/Users/1194361820/QQ/WinTemp/RichOle/0U9%7BVWW%60KM~S2X)VM2QF0N.jpg[/img] 直接点击“Next”&#xff0c;继续&#xff0c;选择安装类型这…

UI设计培训教程分享:UI设计师的色彩使用技巧

作为一名合格的UI设计师&#xff0c;色彩的使用是非常重要的&#xff0c;一个专业的UI设计师对于UI设计色彩的搭配是非常的出色的&#xff0c;下面小编就为大家分享UI设计培训教程&#xff1a;UI设计师的色彩使用技巧 UI设计培训教程分享&#xff1a;UI设计师的色彩使用技巧 一…

hive 使用技巧笔记

来源&#xff1a;http://michael-roshen.iteye.com/blog/2115268 例子&#xff1a; INSERT OVERWRITE TABLE prices_collected_${hiveconf:wid_version} select pc.collect_id as product_id , regexp_extract(pc.price,(\\d*\\.?\\d),1) as price , pc.region, as location…

GNU make manual 翻译( 一百四十九)

继续翻译 5.7.4 The --print-directory Option ------------------------------------If you use several levels of recursive make invocations, the -w or --print-directory option can make the output a lot easier to understand by showing each directory as make sta…

【青少年编程】马雷越:商品价格竞猜

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…