第47章 C±±

 C言語で書いたプログラムをC++で使おうとしても使えない、ということが起こります。今回はその秘密に迫ってみます。


 では、今回の要点です。


 では、いってみましょう。


 今回はちょっと趣を変えてC言語で関数を作ってみましょう。

プログラム1
// Count1.c
// 文字列の中に特定の文字が何個現れるかを数える関数
int strcount(const char* str, int letter)
{
    int i;
    int nCount = 0;
    for(i = 0; str[i] != 0; i++)
    {
        if(str[i] == letter)
            nCount++;
    }
    return nCount;
}

 ...C++と何も変わりませんね。ま、そりゃそうですね。

 では、この関数をC++から使ってみましょう。

プログラム2
// Extern3.cpp
#include <iostream.h>

int strcount(const char* str, int letter);

int main()
{
    char buffer[1024];
    char letter;

    cout << "文字列を入力して下さい > " << flush;
    cin >> buffer;
    cout << "どの文字を数えますか > " << flush;
    cin >> letter;

    cout << '\''<< letter << "\' は "
         << strcount(buffer, (unsigned char)letter)
         << " 個あります。" << endl;

    return 0;
}

 これでいいはずとビルドしてみるも...コンパイルは通りますが、リンクが通りません。エラーメッセージを見てみましょう。

Extern3.obj : error LNK2001: 外部シンボル
""int __cdecl strcount(char const *,int)" (?strcount@@YAHPBDH@Z)" は未解決です

 何と、strcount が見つからないというエラーです。

 しかし、よく見てみると何か変な文字列が書かれています。

?strcount@@YAHPBDH@Z

 そうです。これです。何故見つからないのかのヒントはここにあります。

 今度はCとC++を逆にしてやってみましょう。

プログラム3
// Count2.cpp
// 文字列の中に特定の文字が何個現れるかを数える関数
int strcount(const char* str, int letter)
{
    int i;
    int nCount = 0;
    for(i = 0; str[i] != 0; i++)
    {
        if(str[i] == letter)
            nCount++;
    }
    return nCount;
}
プログラム4
// Extern4.c
#include <stdio.h>

int strcount(const char* str, int letter);

int main()
{
    char buffer[1024];
    char letter;

    printf("文字列を入力して下さい > ");
    scanf("%s", buffer);
    printf("どの文字を数えますか > ");
    scanf("%c", &letter);

    printf("\'%c\' は %d 個あります。\n", letter,
           strcount(buffer, (unsigned char)letter));

    return 0;
}

 今度は次のようなエラーが出ました。

Extern4.obj : error LNK2001: 外部シンボル "_strcount" は未解決です

 このことから言えることは、コンパイルしたあとは関数名が変わってしまっているということです。C言語では "_strcount" に、C++言語では "?strcount@@YAHPBDH@Z" に変わっているのです。

 Cだけ、もしくはC++だけで使うのであれば、どんなに関数名が変わろうと構いません。全てが同じように変わっているからです。

 例えば、「カバ」という存在があります。「カバ」という存在にはもともと名前はありません。人間が勝手に名前を付けているわけです。この存在が "strcount" と対応していると考えて下さい。

 日本語では「カバ」という存在に「カバ」という名前を付けて呼んでいます。勝手に名前を付けているわけですが、日本語を使う限りみんなそう呼んでいるので不自由することはありません。これが "_strcount" と対応しています。

 これはドイツ語でも同じ事が言えます。ドイツ語では「カバ」という存在を「Nilpferd」と呼んでいますが、ドイツ語を使う限りみんなそう呼んでいるので不自由することはありません。これが "?strcount@@YAHPBDH@Z" と対応しています。

 しかし、日本語は知っているがドイツ語を知らない人にとっては「Nilpferd」と言われても何がなんだか分かりませんし、その逆もそうです。これが上のエラーの原因です。

 ではどうすればいいかというと、翻訳してやればいいですね。そのために使うのが extern "<言語名>" です。これを使うと、その言語での名前の付け方を使うようになります。例えば上の例(前者)では、

プログラム5
// Extern3b.cpp
#include <iostream.h>

extern "C" int strcount(const char* str, int letter);

int main()
{
    char buffer[1024];
    char letter;

    cout << "文字列を入力して下さい > " << flush;
    cin >> buffer;
    cout << "どの文字を数えますか > " << flush;
    cin >> letter;

    cout << '\''<< letter << "\' は "
         << strcount(buffer, (unsigned char)letter)
         << " 個あります。" << endl;

    return 0;
}

とすることになります。こうすれば strcount はC言語で作られていると分かり、C言語用の名前の付け方で解釈してくれます

 ではその逆はどうかというと、それはできません。C++言語はC言語より後に作られたので、それを使うような機構はC言語には備わっていないのです。クラスとかを使っているとどうしようもありませんし、あまりそうする機会もないのでどうでもいい話です。どうしても使いたければ、C言語用に書き直してやればいいですね。

 沢山プロトタイプがある場合は、全部に extern "C" をつけて回るのはしんどいので一括して指定することもできます。

extern "C"
{

プロトタイプ

}

 また、C++の時は __cplusplus という組み込みマクロ(第19章参照)が定義されている環境が多いので(注:どの環境でも定義されている保証はありません)、

#ifdef __cplusplus
extern "C"
{
#endif

プロトタイプ

#ifdef __cplusplus
}
#endif

としてやると、CでもC++でも両方とも使えるヘッダファイルを作ることができます。string.h など、C標準のヘッダファイルの中を見るとこういう記述が見られますね。

 実際には、C言語を使って作られたライブラリをC++で使う、もしくは、CからでもC++からでも使えるライブラリを作るというときに使うことになると思います。上の __cplusplus の機構が使われてないヘッダファイルを使うときは、ヘッダファイルを書き換えるのではなく、#include 文を extern "C" { } で囲んでやったので構いませんね。

 あと、関数の定義の方にも extern "C" をつけることができます。でも、あまり使うことはないと思います。


 では、CとC++でなぜ名前の付け方が違うのでしょうか?

 これは、C++では関数のオーバーロードやオーバーライド、または名前空間などがあるからです。つまり、同じ名前の異なる関数がいくつもある可能性があるからなのです。そういうわけで、C++の内部では名前にそういった情報をくっつけて、一意に決まる名前に変換してから扱っているわけです。

 この反面、C言語では同じ名前の関数を作ることができません。そのため、C言語では名前を加工する必要がないのです。VC++でアンダーラインがつけてあるのは...アセンブリ言語の命令とかとかぶらないようにするためだと思います。多分。

 これらの名前付けの方法は環境に依存するかも知れませんが、C++では名前を加工して扱っているという点はどの環境でも言えることです。このことが extern "C" を必要にしているのです。


 では、今回の要点です。


 それでは。


第46章 ハンドルされない例外 | 第48章 同姓同名4

Last update was done on 2001.5.5

この講座の著作権はロベールが保有しています