昨晚看书的时候碰到一个问题,在C++中如何调用C代码...于是查了一下资料...发现了一个大神写的文章挺好的。

-------------------------------------------------------------------------------------------------------------

1、 声明外部变量  

    现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相透明的,也就是说,在编译时,全局变量的可见域限制在文件内部。下面举一个简单的例子。创建一个工程,里面含有A.cpp和B.cpp两个简单的C++源文件:
 //A.cpp
  int i;
  void main()
  {
  }   //B.cpp
  int i;

这两个文件极为简单,在A.cpp中我们定义了一个全局变量i,在B中我们也定义了一个全局变量i。

 

  我们对A和B分别编译,都可以正常通过编译,但是进行链接的时候,却出现了错误,错误提示如下:

 

  Linking...

 

  B.obj : error LNK2005: "int i" (?i@@3HA) already defined in A.obj

 

  Debug/A.exe : fatal error LNK1169: one or more multiply defined symbols found

 

  Error executing link.exe.

 

  A.exe - 2 error(s), 0 warning(s)

 

  这就是说,在编译阶段,各个文件中定义的全局变量相互是透明的,编译A时觉察不到B中也定义了i,同样,编译B时觉察不到A中也定义了i。

 

  但是到了链接阶段,要将各个文件的内容“合为一体”,因此,如果某些文件中定义的全局变量名相同的话,在这个时候就会出现错误,也就是上面提示的重复定义的错误。

 

  因此,各个文件中定义的全局变量名不可相同。

 

  在链接阶段,各个文件的内容(实际是编译产生的obj文件)是被合并到一起的,因而,定义于某文件内的全局变量,在链接完成后,它的可见范围被扩大到了整个程序。

 

  这样一来,按道理说,一个文件中定义的全局变量,可以在整个程序的任何地方被使用,举例说,如果A文件中定义了某全局变量,那么B文件中应可以使用该变量。修改我们的程序,加以验证:

 //A.cpp
  void main()
  {
  i = 100; //试图使用B中定义的全局变量
  }
  //B.cpp
  int i;
  编译结果如下:
  Compiling...
  A.cpp
  C:\Documents and Settings\wangjian\桌面\try extern\A.cpp(5) : error C2065: 'i' : undeclared identifier
  Error executing cl.exe.
  A.obj - 1 error(s), 0 warning(s)
  编译错误。

其实出现这个错误是意料之中的,因为:文件中定义的全局变量的可见性扩展到整个程序是在链接完成之后,而在编译阶段,他们的可见性仍局限于各自的文件。

 

  编译器的目光不够长远,编译器没有能够意识到,某个变量符号虽然不是本文件定义的,但是它可能是在其它的文件中定义的。

 

  虽然编译器不够远见,但是我们可以给它提示,帮助它来解决上面出现的问题。这就是extern的作用了。

 

  extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”

 

  我们为上面的错误程序加上extern关键字:

 //A.cpp
  extern int i;
  void main()
  {
  i = 100; //试图使用B中定义的全局变量
  }
  //B.cpp
  int i;
  顺利通过编译,链接。
函数 extern 函数1
  常见extern放在函数的前面成为函数声明的一部分,那么,C语言关键字extern在函数的声明中起什么作用?

 

  答案与分析:

 

  如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别:

 

  extern int f(); 和int f();

 

  当然,这样的用处还是有的,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,我比较习惯在所有的函数声明前添加extern修饰。

 

  extern 函数2

 

  当函数提供方单方面修改函数原型时,如果使用方不知情继续沿用原来的extern申明,这样编译时编译器不会报错。但是在运行过程中,因为少了或者多了输入参数,往往会造成系统错误,这种情况应该如何解决?

 

  答案与分析:

 

  目前业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供对外部接口的声明,然后调用包含该文件的头文件,从而省去extern这一步。以避免这种错误。

 

  宝剑有双锋,对extern的应用,不同的场合应该选择不同的做法。

 

  extern “C”

 

  在C++环境下使用C函数的时候,常常会出现编译器无法找到obj模块中的C函数定义,从而导致链接失败的情况,应该如何解决这种情况呢?

 

  答案与分析:

 

  C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。

 

  下面是一个标准的写法:

点击(此处)折叠或打开

  1. //在.h文件的头上
  2.   #ifdef __cplusplus
  3.   #if __cplusplus
  4.   extern "C"{
  5.   #endif
  6.   #endif /* __cplusplus */
  7.   …
  8.   …
  9.   //.h文件结束的地方
  10.   #ifdef __cplusplus
  11.   #if __cplusplus
  12.   }
  13.   #endif
  14.   #endif /* __cplusplus */
 

  C++中extern c的深层探索

 

  C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。

 

  2.从标准头文件说起

 

  某企业曾经给出如下的一道面试题:

 

  面试题

 

  为什么标准头文件都有类似以下的结构?

 

  #ifndef __INCvxWorksh

 

  #define __INCvxWorksh

 

  #ifdef __cplusplus

 

  extern "C" {

 

  #endif

 

  /*...*/

 

  #ifdef __cplusplus

 

  }

 

  #endif

 

  #endif /* __INCvxWorksh */

 

  分析

 

  显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。

 

  那么

 

  #ifdef __cplusplus

 

  extern "C" {

 

  #endif

 

  #ifdef __cplusplus

 

  }

 

  #endif

 

  的作用又是什么呢?我们将在下文一一道来。

 

  3.深层揭密extern "C"

 

  extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。

 

  被extern "C"限定的函数或变量是extern类型的;

 

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

 

  extern int a;

 

  仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

 

  引用一个定义在其它模块的全局变量或函数(如,全局函数或变量定义在A模块,B欲引用)有两种方法,

      一、B模块中include模块A的头文件。
      二、模块B中对欲引用的模块A的变量或函数重新声明一遍,并前加extern关键字。

 

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

 

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

 

  被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;

 

  未加extern “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

 

  #include "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"通常的使用技巧。

 

  4.extern "C"的惯用法

 

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

 

  extern "C"

 

  {

 

  #include "cExample.h"

 

  }

 

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

 

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

 

点击(此处)折叠或打开

  1. /* c语言头文件:cExample.h */
  2.   #ifndef C_EXAMPLE_H
  3.   #define C_EXAMPLE_H
  4.   extern int add(int x,int y);
  5.   #endif
  6.   /* c语言实现文件:cExample.c */
  7.   #include "cExample.h"
  8.   int add( int x, int y )
  9.   {
  10.   return x + y;
  11.   }
  12.   // c++实现文件,调用add:cppFile.cpp
  13.   extern "C"
  14.   {
  15.   #include "cExample.h"
  16.   }
  17.   int main(int argc, char* argv[])
  18.   {
  19.   add(2,3);
  20.   return 0;
  21.   }

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

 

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

 

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

 

 

点击(此处)折叠或打开

  1. //C++头文件 cppExample.h
  2.   #ifndef CPP_EXAMPLE_H
  3.   #define CPP_EXAMPLE_H
  4.   extern "C" int add( int x, int y );
  5.   #endif
  6.   //C++实现文件 cppExample.cpp
  7.   #include "cppExample.h"
  8.   int add( int x, int y )
  9.   {
  10.   return x + y;
  11.   }
  12.   /* C实现文件 cFile.c
  13.   /* 这样会编译出错:#include "cppExample.h" */
  14.   extern int add( int x, int y );
  15.   int main( int argc, char* argv[] )
  16.   {
  17.   add( 2, 3 );
  18.   return 0;
  19.   }

(转)C++中使用C代码的更多相关文章

  1. linux内核分析作业4:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    系统调用:库函数封装了系统调用,通过库函数和系统调用打交道 用户态:低级别执行状态,代码的掌控范围会受到限制. 内核态:高执行级别,代码可移植性特权指令,访问任意物理地址 为什么划分级别:如果全部特权 ...

  2. Gradle中的buildScript代码块

    在编写Gradle脚本的时候,在build.gradle文件中经常看到这样的代码: build.gradle 1 2 3 4 5 6 7 8 9 buildScript { repositories ...

  3. Oracle中经典分页代码!

    在Oracle中因为没有top关键字,所以在sqlserver中的分页代码并不适用于Oracle,那么在Oracle中如何来实现分页呢? --查询所有数据 STUNO STUNAME STUAGE S ...

  4. python学习之——计算给出代码中注释、代码、空行的行数

    题目:计算给出代码中注释.代码.空行的行数 来源:网络 思路:注释行以 ‘#’开头,空行以 ‘\n’ 开头,以此作为判断 def count_linenum(fname): fobj = open(f ...

  5. netbeans中实体类代码的bug

    用了netbeans中实体类代码后,忽然报错: com.sun.tools.javac.code.Symbol$CompletionFailure: 找不到sun.util.logging.Platf ...

  6. 在Flex (Flash)中嵌入HTML 代码或页面—Flex IFrame

    在flex组件中嵌入html代码,可以利用flex iframe.这个在很多时候会用到的,有时候flex必须得这样做,如果你不这样做还真不行-- flex而且可以和html进行JavaScript交互 ...

  7. Scala 深入浅出实战经典 第45讲: scala中context bounds代码实例

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  8. Myeclipse中把java代码导成UML类图

    Myeclipse中把java代码导成UML类图 1.右键点击项目名称,选择New-------àUML2 Model 2.给类图命名 3.导成类图 1)如果要把整个项目导成类图,则把整个项目拖到类图 ...

  9. jsp页面中的java代码

    jsp页面中的java代码 1.jsp表达式  <%= ....%>  只能放置一个变量常量 2. jsp小脚本 <% .... %>  java语句,可以插入一些语句 3. ...

  10. Android WebView中的JavaScript代码使用

    在WebView中使用JavaScript 如果你想要载入的页面中用了JavaScript,你必须为你的WebView使能JavaScript. 一旦使能之后,你也可以自己创建接口在你的应用和Java ...

随机推荐

  1. 面试题12:打印1到最大的n位数

    题目:输入数字n,按顺序打印出从1最大的n位十进制数.比如输入3,则打印出1.2.3一直到最大的3位数即999. 考点:大数问题. 解决方案:在字符串上模拟数字加法. <剑指Offer>上 ...

  2. FAT-fs (mmcblk0p1): Volume was not properly unmounted. Some data may be corrupt. Please run fsck.

    /******************************************************************************** * FAT-fs (mmcblk0p ...

  3. Apache POI实现excel导出

    链接:http://poi.apache.org/ Excel数据导出步骤: 使用poi 完成账户数据的导出功能 导入poi  jar包并添加到classpath中 1.查询数据 2.定义导出头 St ...

  4. VMware12版虚拟机怎么安装win7系统(详细教程

    转自:http://jingyan.baidu.com/article/cd4c29791fcf1b756e6e6034.html VMware12版虚拟机怎么安装win7系统(详细教程) 现 在很多 ...

  5. vue-router导航守卫

    导航守卫 译者注 “导航”表示路由正在发生改变. 正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航.有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件 ...

  6. 预备架构的工具ADMEMS矩阵

    矩阵,是很多著名方法的核心.例如,制定公司层战略的方法之一是"波士顿矩阵","波士顿矩阵"又称"市场增长率-相对市场份额矩阵". " ...

  7. HDU1423:Greatest Common Increasing Subsequence

    浅谈\(DP\):https://www.cnblogs.com/AKMer/p/10437525.html 题目传送门:http://acm.hdu.edu.cn/showproblem.php?p ...

  8. 在<img src="..." title="..."> 中使title的内容换行的方法

    在<img src="..." title="...">中要使TITILE的内容换行,不能使用html标签,只能用ASCII码,方法如下: < ...

  9. Hibernate基础(一)

    Hibernate是ORM开源组件 源码:http://sourceforge.net/projects/hibernate/ 1.Hibernate的配置文件. 默认配置文件: hibernate. ...

  10. java代码继承super

    总结:多态 :. 当重写父类的方法的时,子类对象名可以调用父类的方法,以及不带参的构造方法 package com.addd; public class rr { int a, b; String c ...