extern C

8642 ワード

http://www.cppblog.com/Macaulish/archive/2008/06/17/53689.html
1.引用
C++言語の作成の目的は「a better C」ですが、C++のようなC言語のグローバル変数や関数が採用するコンパイルや接続方式がC言語と完全に同じであることを意味するわけではありません.Cと互換性のある言語として、C++はプロセス言語の一部の特徴(世間では「徹底的にオブジェクトに向かっていない」と呼ばれている)を保持しているため、クラスに属さないグローバル変数と関数を定義することができます.しかし、C++はあくまでオブジェクト向けのプログラム設計言語であり、関数のリロードをサポートするために、C++のグローバル関数に対する処理方式はCとは明らかに異なる.
2.標準ヘッダファイルから
ある企業は、次のような面接問題を出したことがあります.
面接問題
なぜ標準ヘッダファイルには以下のような構造があるのですか?

  
  
  
  
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
/*...*/ #
extern "C" { #endi fifdef __cplusplus } #endif
     , “#ifndef
#endif /* __INCvxWorksh */
 __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在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

  通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字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関数例プロジェクトに含まれる3つのファイルのソースコードは以下の通りである.
/* 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"{}を追加します.
(2)CでC++言語の関数と変数を参照する場合、C++のヘッダファイルにはextern"C"を追加する必要がありますが、C言語ではextern"C"を宣言したヘッダファイルを直接参照することはできません.CファイルではC++で定義したextern"C"関数のみをexternクラス型として宣言する必要があります.
筆者が作成したC引用C++関数例プロジェクトに含まれる3つのファイルのソースコードは以下の通りである.
//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;
}

第3節で述べたextern"C"がコンパイルと接続の段階で果たす役割を深く理解すれば、本節で述べたC++からC関数を参照する慣用とC++関数を参照する慣用を本当に理解することができる.第4節で与えられたサンプルコードについては、特に詳細に注意する必要がある.
extern「C」は双方向で使用する文法表現である.つまり、cppがcヘッダファイルを参照する場合、またはcがcppファイルを参照する場合に使用する必要がある.ただし、extern「C」は、cpp参照時にのみ表示され、c参照時には存在しない.cppがcの関数を参照する場合、cppが使用するヘッダファイルにextern「C」を宣言する必要がある、cがcppの関数を参照する場合、cppで使用するヘッダファイルにextern"C"を使用する必要があります.コンパイラは、コンパイル時に関数名を特殊に処理してcで参照できるように宣言します.宣言しない場合、cがこのヘッダファイルを参照すると、cppの関数命名規則には変数タイプが含まれており、cコンパイル後の関数名には含まれていないため、関数が見つかりません.