extern "C"详解

由一道面试题说起
面试题:为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
分析:
1 显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。
2 那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的作用又是什么呢?
extern "C" 目的:实现C++与C及其它语言的混合编程
1、在cpp文件中调用c文件中实现的函数的时候,需要用extern "C"声明该函数,否则cpp会按名字改编后的
函数名去找该函数而找不到。
cpp文件调用c文件中函数如下:
   c文件中有一函数:
              void Transfer(int a; char b);
   cpp文件中必须用extern "C"声明该函数如下才可以实行调用:
            extern "C" void Transfer(int a; char b);
2、在cpp文件中实现的函数,c文件若要调用,就必须在cpp文件中用extern "C"来声明该函数,否则cpp在编译过程中就
会对其进行名字改编,c文件就找不到该函数的原型。
c文件调用cpp文件中函数如下:
   cpp文件中有一函数:
               void Transfer(int a; char b);
   但必须用extern "C"来声明后,c文件才可以调用void Transfer(int a; char b)函数。如下:
                extern "C" void Transfer(int a; char b);

关键字extern "C"
   extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义:
1 被extern "C"限定的函数或变量是extern类型的;
   extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。
记住,下列语句:
extern int a;
仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
    与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。
2 被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;
    未加extern “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。
参考:http://blog.csdn.net/weiqubo/archive/2009/10/16/4681813.aspx

一个例子
前些天,编程序是用到了很久以前写的C程序,想把里面的函数利用起来,连接发现出现了找不到具体函数的错误:
以下是旧的C程序库

C的头文件
/*-----------c.h--------------*/
#ifndef _C_H_
#define _C_H_
extern int add(int x, int y);
#endif

C的源文件
/*-----------c.c--------------*/
int add(int x, int y){
        return x+y;
}

C++的调用
/*-----------cpp.cpp--------------*/
#include "c.h"
void main()
{
        add(1, 0);
}
这样编译会产生错误cpp.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z),原因是找不到add的目标模块。
这才令我想起C++重载的函数命名方式和C函数的命名方式,让我们回顾一下:C中函数编译后命名会在函数名前加以"_",比如add函数编译成obj文件时的实际命名为_add,而c++命名则不同,为了实现函数重载同样的函数名add因参数的不同会被编译成不同的名字
例如
int add(int , int)==>add@@YAHHH@Z,
float add(float , float )==>add@@YAMMM@Z,
以上是VC6的命名方式,不同的编译器会不同,总之不同的参数同样的函数名将编译成不同目标名,以便于函数重载是调用具体的函数。
编译cpp.cpp中编译器在cpp文件中发现add(1, 0);的调用而函数声明为extern int add(int x, int y);编译器就决定去找add@@YAHHH@Z,可惜他找不到,因为C的源文件把extern int add(int x, int y);编译成_add了;
为了解决这个问题C++采用了extern "C",这就是我们的主题,想要利用以前的C程序库,那么你就要学会它,我们可以看以下标准头文件你会发现,很多头文件都有以下的结构

#ifndef __H
#define __H
#ifdef __cplusplus
extern "C" {
#endif

extern int f1(int, int);
extern int f2(int, int);
extern int f3(int, int);

       
#ifdef __cplusplus
}
#endif
#endif /*__H*/

如果我们仿制该头文件可以得到:
#ifndef _C_H_
#define _C_H_
#ifdef __cplusplus
extern "C" {
#endif

extern int add(int, int);

#ifdef __cplusplus
}
#endif
#endif /* _C_H_ */
这样编译
/*-----------c.c--------------*/
int add(int x, int y){
return x+y;
}
这时源文件为*.c,__cplusplus没有被定义,extern "C" {}这时没有生效对于C他看到只是extern int add(int, int);
add函数编译成_add(int, int);
而编译c++源文件
/*-----------cpp.cpp--------------*/
#include "c.h"
void main()
{
add(1, 0);
}
这时源文件为*.cpp,__cplusplus被定义,对于C++他看到的是extern "C" {extern int add(int, int);}编译器就会知道 add(1, 0);调用的C风格的函数,就会知道去c.obj中找_add(int, int)而不是add@@YAHHH@Z;
这也就为什么DLL中常看见extern "C" {},windows是采用C语言编制他首先要考虑到C可以正确调用这些DLL,而用户可能会使用C++而extern "C" {}就会发生作用

你可能感兴趣的:(extern "C"详解)