首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

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

extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加extern "C"声明时的连接方式

假设在C++中,模块A的头文件如下:

// 模块A头文件  moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, int y );

#endif

在模块B中引用该函数:

// 模块B实现文件  moduleB.cpp

#i nclude "moduleA.h"

foo(2,3);

实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

加extern "C"声明后的编译和连接方式

加extern "C"声明后,模块A的头文件变为:

// 模块A头文件  moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

extern "C" int foo( int x, int y );

#endif

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。

明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:

extern "C"的惯用法

(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"

{

#i nclude "cExample.h"

}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

C++引用C函数例子工程中包含的三个文件的源代码如下:

/* c语言头文件:cExample.h */

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

extern int add(int x,int y);

#endif

/* c语言实现文件:cExample.c */

#i nclude "cExample.h"

int add( int x, int y )

{

return x + y;

}

// c++实现文件,调用add:cppFile.cpp

extern "C"

{

#i nclude "cExample.h"

}

int main(int argc, char* argv[])

{

add(2,3);

return 0;

}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" {  }。

(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。

C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h

#ifndef CPP_EXAMPLE_H

#define CPP_EXAMPLE_H

extern "C" int add( int x, int y );

#endif

//C++实现文件 cppExample.cpp

#i nclude "cppExample.h"

int add( int x, int y )

{

return x + y;

}

/* C实现文件 cFile.c

/* 这样会编译出错:#i nclude "cExample.h" */

extern int add( int x, int y );

int main( int argc, char* argv[] )

{

add( 2, 3 );

return 0;

}

在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”的更多相关文章

  1. C++程序中调用WebService的实现

    前言 因为最近的项目中需要运用到在MFC程序中调用WebService里面集成好了的函数,所以特意花了一天的时间来研究WebService的构建以及如何在MFC的程序中添加Web引用,进而来实现在C+ ...

  2. Notepad++中调用cl.exe编译器(Windows)

    Notepad++中调用cl.exe编译器(Windows) 近来在notepad++中写代码,写完后总是习惯性的想去VS里面编译一下,看看代码是否有误.但有时候一些零碎的小文件总是懒得再VS中打开, ...

  3. 从C#程序中调用非受管DLLs

    从C#程序中调用非受管DLLs 文章概要: 众所周知,.NET已经渐渐成为一种技术时尚,那么C#很自然也成为一种编程时尚.如何利用浩如烟海的Win32 API以及以前所编写的 Win32 代码已经成为 ...

  4. 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案

    方案特点: 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案,简化软件开发流程,减少各应用系统相同模块的重复开发工作,提高系统稳定性和可靠性. 基于HTTP协议的开发接口 使用特点在网页 ...

  5. iOS程序中调用系统自带应用(短信,邮件,浏览器,地图,appstore,拨打电话,iTunes,iBooks )

    在网上找到了下在记录下来以后方便用 在程序中调用系统自带的应用,比如我进入程序的时候,希望直接调用safar来打开一个网页,下面是一个简单的使用:

  6. Java程序中调用Python脚本的方法

    在程序开发中,有时候需要Java程序中调用相关Python脚本,以下内容记录了先关步骤和可能出现问题的解决办法. 1.在Eclipse中新建Maven工程: 2.pom.xml文件中添加如下依赖包之后 ...

  7. Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)

    文章目录:                   1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...

  8. 如何在程序中调用Caffe做图像分类

    Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点.学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的 ...

  9. 利用 gnuplot_i 在你的 c 程序中调用 GNUPLOT

    这是一篇非常早曾经写的小文章,最初发表于我的搜狐博客(2008-09-23 22:55).由于自从转移到这里后,sohu 博客就不再维护了,所以把这篇文章也一起挪了过来. GNUPLOT 是一款功能强 ...

随机推荐

  1. CodeForces 568B DP Symmetric and Transitive

    题意: 根据离散数学的内容知道,一个二元关系是一个二元有序组<x, y>的集合. 然后有一些特殊的二元关系,比如等价关系,满足三个条件: 自反性,任意的x,都有二元关系<x, x&g ...

  2. asp.net多线程在web页面中简单使用

    需求:一个web页面 default.aspx 里面有两个控件GridView1,GridView2,通过两个线程分别加载绑定数据. 绑定GridView1:void BindCategory()  ...

  3. SSM网上商城项目 01

    开发环境与技术选型 操作系统:win7 IDE:Eclipse neno JDK:1.8 数据库:mysql5.6 Dao层:mybatis.数据库连接池(德鲁伊druid) 缓存:redis3.0. ...

  4. 用上GIT你一定会爱上他

    前言 Git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理. Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控 ...

  5. js中的事件委托和事件代理详解

    起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...

  6. 设计模式之工厂模式 Factory实现

    simpleFactory //car接口 public interface Car { void run(); } //两个实现类 public class Audi implements Car{ ...

  7. codeM预赛

    [编程|1000分] 音乐研究 时间限制:1秒空间限制:32768K 题目描述 美团外卖的品牌代言人袋鼠先生最近正在进行音乐研究.他有两段音频,每段音频是一个表示音高的序列.现在袋鼠先生想要在第二段音 ...

  8. 九度oj 题目1362:左旋转字符串(Move!Move!!Move!!!)

    题目描述: 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果.对于一个给定的字符序列S,请你把其循环左移K位后的序列输出.例如,字符序列S=”ab ...

  9. iOS----精品开源库-开发强力助攻

    30个精品iOS开源库,超强助攻 你不会想错过他们,真的. 我爱开源.  文章的尾部你会看到一个太长不看的版本——一个简单的列表,只有标题和到项目的链接.如果你发现这篇文章是有用的,把它和你的iOS开 ...

  10. 【Luogu】P2057善意的投票(最小割转最大流)

    题目链接 也算水题一道吧,不过Round1感性理解一下就xjb建了个图,40 Round2仔细分析了一会,理性建了个图,90 然后分析了半天……改大数组就A了…… 从S到所有值为1的点连一条inf的边 ...