シンボル修飾と関数署名、extern“C”

8660 ワード

参考資料:
『プログラマーの自己修養』3.5.3および3.5.4小節.
 
シンボル修飾の由来
1970年代以前、コンパイラがコードをコンパイルする際に発生したターゲットファイルでは、シンボル名は対応する変数や関数の名前と同じであり、プログラミング言語の発展、例えばC言語に伴い、1つのC言語プログラムがこれらのライブラリを使用する場合、それらのライブラリに宣言された関数や変数の名前をシンボル名として使用することはできなかった.それ以外の場合、既存のターゲットファイルと名前が競合します.
このようなシンボル名の競合を防止するために,各プラットフォームにおけるプログラミング言語は,それぞれのシンボル生成文法を規定している.CのようにUNIXの下で関数名と変数の前に下線を符号名として付けます.このように関数名に特定の記号を追加して、その記号名を一意にする方法は、記号修飾である.
このような単純なシンボル修飾は、シンボル競合の問題を根本的に解決するものではなく、プログラミング言語のように作成されたターゲットファイル間でシンボル競合が発生する可能性があり、プログラムが大きい場合、異なる部門間でシンボル競合が発生する可能性があり、C++のような言語にネーミングスペース(namespace)を追加してマルチモジュールシンボル競合の問題を解決し始めた.
C++がクラス、継承、ダミーメカニズム、リロード、ネーミングスペースなどの特性を持つことをサポートするために、シンボル修飾(またはシンボル改編と呼ばれる)が発明された.、修飾される前の関数名およびその戻り値やパラメータなどの情報を関数署名と呼ぶ.異なるコンパイラは、異なるコンパイラコンパイルによって生成されるターゲットファイルが正常に相互にリンクできないため、異なる名前修飾方法を採用する.
 
extern “C”
C++Cとの互換性のため、シンボルの管理上、C++には、Cのシンボルを宣言または決定するための「extern」C「」キーワードの使用方法があります.
extern "C" int add(int a, int b);

extern "C" int a;

//   

//extern "C"

//{

//    int add(int a, int b);

//    int a;

//};

注意:extern「C」のCは大文字でなければなりません.
C++コンパイラはextern「C」の括弧内(または後の)コードをC言語コードとして処理するので、add関数と変数aをCと宣言するためのコードであることは明らかです.彼らの定義が.cファイルに置かれている可能性があるからです.
例:
// c_code.h file in c_project project

#pragma once

int add(int a, int b);
// c_code.c file in c_project project

#include "c_code.h"

int  add(int a, int b)

{

    return a + b;

}

main.cがあれば、コードは次の通りです.
#include "c_code.h"

#include "stdio.h"

#include <conio.h>

int main(void)

{

    printf("%d + %d = %d
", 3, 3, add(3,3)); _getch(); return 0; }

コンパイルリンクの実行は間違いありません.では、main.cファイルの名前をmain.cppに変更します.コードは変更されません.リンクエラーが発生します.error LNK 209:unresolved external symbol“int__cdecladd(int,int)”(?add@@YAHHH@Z)referenced in function_main.ここでのリンクエラーは、シンボル“?add@@YAHHH@Zが見つからないことを意味する.このシンボルは、「int__cdecladd(int,int)」修飾後のシンボル名である.
なぜなら、Cのシンボル修飾方式とC++のシンボル修飾方式が異なるため、同じ宣言で異なるシンボル名が生成されます.ここでmain.cppはcppファイルであり、cpp方式でリンクをコンパイルしますが、addの定義は.cファイルの中にあるのでリンクできません.
次にextern「C」を使用して、add関数がmain.cppでリンクをコンパイルするときに、Cのシンボル修飾方式を使用する方法を試みます.ここでは2つの方法があります.
extern "C" int add(int a, int b);        //      add  ,   c_code.h   

int main(void)

{

    printf("%d + %d = %d
", 3, 3, add(3,3)); _getch(); return 0; }
extern "C"            //     c_code.h       C    

{

    #include "c_code.h"

}

#include "stdio.h"

#include <conio.h>

int main(void)

{

    printf("%d + %d = %d
", 3, 3, add(3,3)); _getch(); return 0; }

以上の2つの方法は同じですが、ここではc_code.hには1つの関数しかないので、1つ目の方法を使うのも簡単ですが、ヘッダファイルに多くの関数宣言があれば、2つ目の方法を使うのは簡単です.
C++はC言語の拡張であるため、Cのライブラリ関数を使用する必要があります.これらのライブラリ関数の定義は.cファイルで実現されています.では、ライブラリ関数を使用するたびにextern「C」キーでヘッダファイルを修飾することを避けるために、これらの標準ライブラリヘッダファイルには、この問題を解決するために次のコードが含まれていることがよくあります.
// c_code.h file in c_project project

#pragma once



#ifdef __cplusplus

extern "C"{

#endif



    int add(int a, int b);



#ifdef __cplusplus

}

#endif

コンパイル時に__cplusplusマクロが定義されていることが判明した場合、後の関数宣言にextern"C"を付けてCで修飾し、そうでなければ処理しない.一方、__cplusplusマクロはC++コンパイラがC++プログラムをコンパイルする際にデフォルトで定義したマクロである(実際には.cppファイルの上部にマクロを定義することもできます)、明らかに.cppファイルにこのようなヘッダファイルが含まれている場合、これらの関数はCで修飾されると宣言されますが、.cファイルに含まれている場合、extern「C」修飾は加算されません.これは、main.cが直接コンパイルされ、main.cppができない理由を説明しています.
 
後記
実はこのブログは主にextern「C」を紹介するためですの使い方は、ネットでいろいろと紹介されていて、私も何編も読みましたが、あまり透徹していない感じがしたので、この参考書を探してみましたが、読み終わってから分かりました.だから、いくつかの知識は簡単そうに見えますが、よく理解していなければ、忘れやすいです.