`
vaqeteart
  • 浏览: 304038 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

关于C++中的'extern "C"'

 
阅读更多
关于C++中的'extern "C"'
本文将介绍C++中如何通过extern "C"关键字支持C语言。

主要内容:
简介
1,问题的引出
2,关于extern "C"
3,用法举例
总结

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


1,问题的引出
===============
(1)某企业曾经给出如下的一道面试题:
为什么标准头文件都有类似以下的结构?
//incvxworks.h
#ifndef __INCvxWorksh
#define __INCvxWorksh

#ifdef __cplusplus
extern "C" {
#endif

/*...*/

#ifdef __cplusplus
}
#endif

#endif /* __INCvxWorksh */

(2)问题分析
对于上面问题,显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。
那么,
#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif
的作用又是什么呢?我们将在后面对此进行详细说明。

2,关于extern "C"
===============
前面的题目中的__cplusplus宏,是用来识别编译器的,也就是,将当前代码编译的时候,是否将代码作为C++进行编译。如果是,则定义了__cplusplus宏。更多内容,这里就不详细说明了。
而题目中的extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是"extern"的;其次,被它修饰的目标是"C"的。具体如下:
(1)被extern "C"限定的函数或变量是extern类型的。
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。注意,语句"extern int a;" 仅仅是对变量的声明,其并不是在定义变量a,声明变量并未为a分配内存空间。定义语句形式为"int a;",变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
在引用全局变量和函数之前,必须要有这个变量或者函数的声明(或者定义)。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern "C"修饰。

(2)被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。
(2.1)首先看看C++中,在未加extern "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++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

(2.2)其次,看看在未加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这样的符号!
对于上面例子,如果B模块是C程序,而A模块是C++库头文件的话,会导致链接错误;同理,如果B模块是C++程序,而A模块是C库的头文件也会导致错误。

(2.3)再者,看看加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 ),其结果,将会是C语言的编译连接方式:
(a)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(b)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数(因为这样的声明没有使用extern "C"指明采用C语言的编译链接方式);反之亦然。
所以,综上可知,extern "C"这个声明的真实目的,就是实现C++与C及其它语言的混合编程。

3,用法举例
===============
(1)C++引用C函数的具体例子
在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern "C"
{
#include "cExample.h"
}
因为,C库的编译当然是用C的方式生成的,其库中的函数标号一般也是类似前面所说的_foo之类的形式,没有任何参数信息,所以当然在C++中,要指定使用extern "C",进行C方式的声明(如果不指定,那么C++中的默认声明方式当然是C++方式的,也就是编译器会产生_foo_int_int之类包含参数信息的、C++形式的函数标号,这样的函数标号在已经编译好了的、可以直接引用的C库中当然没有)。通过头文件对函数进行声明,再包含头文件,就能引用到头文件中声明的函数(因为函数的实现在库中呢,所以只声明,然后链接就能用了)。
而在C语言中,对其外部函数只能指定为extern类型,因为C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时,当然会出现编译语法错误。

下面是一个具体代码:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif

/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}

// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
可见,如果C++调用一个C语言编写的.DLL时,在包含.DLL的头文件或声明接口函数时,应加extern "C" { }。这个时候,其实extern "C"是在告诉C++,链接C库的时候,采用C的方式进行链接(即寻找类似_foo的没有参数信息的标号,而不是默认的_foo_int_int这样包含了参数信息的C++标号了)。

(2)C引用C++函数的具体例子
在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件(因为C语言不支持extern "C"关键字,所以会报编译错误),应该仅在C文件中用extern声明C++中定义的extern "C"函数(就是C++中用extern "C"声明的函数,在C中用extern来声明一下,这样C就能引用C++的函数了,但是C中是不用用extern "C"的)。

下面是一个具体代码:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif

//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}

/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );

int main( int argc, char* argv[] )
{
add( 2, 3 );   
return 0;
}

上面例子,C实现文件cFile.c不能直接用#include "cExample.h"因为C语言不支持extern "C"关键字。这个时候,而在cppExample.h中使用extern "C"修饰的目的是为了让C++编译时候能够生成C形式的符号(类似_foo不含参数的形式),然后将其添加到对应的C++实现库中,以便被C程序链接到。


(3)对__BEGIN_DECLS 和 __END_DECLS 的理解
在C语言代码中头文件中,经常看到充斥着下面的代码片段:
   1. __BEGIN_DECLS
   2. .....
   3. .....
   4. __END_DECLS
其实,这些宏一般都是在标准库头文件中定义好了的,例如我当前机器的sys/cdefs.h中大致定义如下:
   1. #if defined(__cplusplus)
   2.        #define __BEGIN_DECLS extern "C" {
   3.        #define __END_DECLS }
   4.        #else
   5.        #define __BEGIN_DECLS
   6.        #define __END_DECLS
   7. #endif

这目的当然是扩充C语言在编译的时候,按照C++编译器进行统一处理,使得C++代码能够调用C编译生成的中间代码。
由于C语言的头文件可能被不同类型的编译器读取,因此写C语言的头文件必须慎重。


总结
===============
(1)extern "C"只是C++的关键字,不是C的。所以,如果在C程序中引入了extern "C"会导致编译错误。
(2)被extern "C"修饰的目标一般是对一个C或者C++函数的声明。从源码上看extern "C"一般对头文件中函数声明进行修饰。无论C程序中的还是cpp中的头文件,其函数声明的形式都是一样的(因为两者语法基本一样),对应声明的实现却可能由于相应的程序特性而不同了(C库和C++库里面当然会不同)。
(3)extern "C"这个关键字声明的真实目的,就是实现C++与C及其它语言的混合编程。一旦被extern "C"修饰之后,它便以C的方式工作,可以实现在C中引用C++库的函数,也可以C++中引用C库的函数。

参考资料:
http://hi.baidu.com/xiaoxiaolq/blog/item/1edc2af30dd4915a342acc5e.html
http://www.cnblogs.com/xiaoxibo/

以上,是对extern "C"这个关键字的理解和总结,如果具体问题想要讨论或者发现有何遗漏之处,可以与我联系。谢谢!
作者:QuietHeart
Email:quiet_heart000@126.com
日期:2011年11月15日
分享到:
评论

相关推荐

    学习总结:C++中extern “C”含义深层探索.doc

    当在C++中使用`extern "C"`时,编译器将跳过C++特有的名称修饰过程,使得函数和变量的名称保持与C语言中的名称一致。这对于C和C++混合编程非常重要,因为C语言编译的库中的函数名称在链接时需要与C++代码中的名称...

    探索C++的秘密之详解extern

    在C语言中,则没有这种重载机制,因此C编译器生成的目标代码中的函数名并不包含这些额外的信息。 举一个简单的例子,如果我们有两个重载的函数: ```cpp void print(int a); void print(double a); ``` 在C++中,...

    C++语言extern C浅析.zip

    在C++编程中,`extern "C"`是一个特殊的声明,用于告诉编译器按照C语言的规则处理特定的函数和变量。这是因为C++支持名称修饰(Name Mangling),即编译器为了支持函数重载和其他高级特性,会将函数和全局变量的名字...

    extern在C和C++中的作用

    - **`extern "C"`的作用**:当在C++环境中使用C语言库时,需要通过`extern "C"`来避免名字修饰的发生,从而确保函数能够正确地被链接器识别。 #### 使用示例 假设有一个C语言定义的函数`void myFunc(int x);`,...

    C++ extern用法

    在 C++ 中,extern “C” 是一种特殊的语法,它用于指定 C 语言风格的函数名,以便在 C++ 环境中使用 C 语言的函数。例如: extern "C" void foo(); 这意味着函数 foo 的定义按 C 语言的方式进行 linkage。 ...

    C++中的extern “C”用法详解

    在C++编程中,`extern "C"` 是一个特殊的语法,用于指示编译器按照C语言的规则处理特定的变量和函数,目的是为了保持与C语言的兼容性。C++和C语言在编译和链接过程中对符号的处理方式有所不同,这可能会导致使用C++...

    C/C++中extern "C" 的作用分析

    总结起来,`extern "C"` 是C++提供的一种兼容C语言的机制,它允许我们在C++项目中安全地使用C语言编写的库或函数,同时也能让C语言代码调用C++的函数,从而实现了C和C++的混合编程。在编写涉及跨语言接口的代码时,...

    关于externC的超详解

    在C++编程中,`extern "C"`是一个特殊的声明,用于告诉编译器按照C语言的规则处理特定的函数和变量。...在阅读“关于externC.txt”这个文件时,你可以期待深入学习到更多关于`extern "C"`的具体细节和实际应用案例。

    C语言调用C++类中的方法

    // 在C语言中声明对应的C++方法 extern cpp_method_ptr cplusplus_method; ``` 然后,在C++源文件中,我们需要将C++方法的地址赋值给这个函数指针: ```cpp cpp_method_ptr cplusplus_method = cplusplus_method; ...

    C/C++ 中extern关键字详解

    C/C++ 中extern关键字详解 在C/C++编程过程中,经常会进行变量和函数的声明和定义,各个模块间共用同一个全局变量时,此时extern就派上用场了。 定义 extern可以置于变量或者函数前,以标示变量或者函数的定义在别的...

    static,extern,全局变量的引用(c_c++)

    通过对`static`、`extern`以及全局变量引用的介绍和分析,我们可以清楚地认识到这些概念在C/C++编程中的重要性。合理地使用这些特性可以帮助我们更好地管理程序中的数据共享问题,同时也能提高程序的可读性和可维护...

    C语言中的extern关键字详细讲述

    `extern`是C语言中的一个关键字,主要用于声明一个变量或者函数的定义存在于当前编译单元之外的地方。当我们使用`extern`声明一个变量或函数时,编译器会认为该变量或函数是在其他地方定义的,当前文件只是引用它...

    C语言代码用在c++环境中

    这对于确保C++代码能够正确地引用或定义C语言中的函数和变量至关重要。 1. **链接约定(Linkage Convention)**:链接约定决定了函数和变量如何在程序的不同部分之间共享。C语言使用的是C链接约定,而C++则支持C++...

    浅析extern “C”的作用

    extern "C"是C++编程语言中的一个关键字,它用于告诉C++编译器按照C语言的方式处理接下来的函数声明。这样做的原因主要是因为C++和C语言在编译过程中对于函数名的处理方式存在差异,从而导致C++代码中不能直接调用...

    extern c 用法解析

    在C++编程中,`extern "C"`是一个特殊的声明,用于指示编译器按照C语言的规则处理函数和全局变量,而不是C++的标准方式。由于C++支持函数重载和名称修饰(name mangling),这可能导致C++编译的代码与C编译的代码在...

    C++中extern关键字使用

    在C++中,为了使C++代码能调用C语言编写的函数,需要使用`extern "C"`来确保名称修饰(name mangling)不会发生。C++的名称修饰是其编译器为支持函数重载而引入的一种机制,它会将函数名转换为一个独特的标识符。...

    实例详解C/C++中extern关键字

    当C++代码需要调用C语言编写的库函数时,如果不使用extern "C"来指定,C++编译器可能会按照C++的方式对函数名进行处理,导致链接错误,因为库中的函数名并非按照C++的方式编译的。使用extern "C"时,编译器则会按照...

Global site tag (gtag.js) - Google Analytics