2013年2月3日日曜日

リンクエラーあるある

自分的リンクエラーのあるある。
  1. -lstdc++のつけ忘れ
  2. extern C
  3. リンクの順序



-lstdc++のつけ忘れ


というか、そもそもつけることを知らなかったり。
初めてC++をやってみようと思いネットで適当に見つけた
以下のようなサンプルをコンパイルしようとして最初に陥る罠
自分も学生時代に踏みました。

#include <iostream>

using namespace std;

int main()
{ 
    cout << "HelloWorld" << endl; 
    return 0; 
}

一番シンプルなHelloWorldですが,これを打ち込んで以下のようにコンパイルしようものなら。
$ gcc test.cpp -o test_out
test.cpp:(.text+0x14): undefined reference to `std::cout'
中略
collect2: ld はステータス 1 で終了しました

となります。
えー!ですよ。涙目で作成したプログラムを見直すのですが、間違いもなく。
そもそもコピペだし。
当時はコンパイルとリンクの差もよくわかっていなかったので、はまりました。
結局以下のように-lstdc++をつければ解決なのですが。

$ gcc test.cpp -o test_out -lstdc++

標準ライブラリを明示的に指定しないと使えないってどうなん。と今でも思います。
この前C++の誕生の歴史を本で読んでちょっと納得しかけましたが、まだ不満は残ってます。


extern C


C++はC言語の拡張として開発されてたためC言語の文法がそのままとおります。
と言う話はよく聞きますが、実際混ぜて作ってみるとハマります。
C++からCを呼び出す場合

C言語ファイル

#include <stdio.h>
#include "linktestlib.h"

void print_lang()
{
    printf("Hello C\n");
}

C言語ヘッダ

void print_lang();
呼出側のC++


#include "linktestlib.h"

int main()
{
    print_lang();
    return 0;
}

これをコンパイルすると。
$ gcc test.cpp linktestlib.c -o linktest
/tmp/cczMjmEx.o: In function `main':
test.cpp:(.text+0x7): undefined reference to `print_lang()'
collect2: ld はステータス 1 で終了しました

見事にリンクエラーですね。
これは、C++には関数のオーバロード(同じ名前でも引数によって別の関数として扱う文法)があるので、
関数名を引数も含めた記号にして(manglingして)扱っています。
一方C言語は、関数名だけで扱っています。
結果、リンカは単純に同じ名前の関数を探しているだけなので、C++の関数からC言語の関数が見つけられなくなります。
この現象はextern CでコンパイラにC言語だよと教えてあげると回避できます。
#ifdef __cplusplus
extern "C"{
#endif
void print_lang();
#ifdef __cplusplus
}
#endif

#ifdef __cplusplusで囲っているのはC言語のときにコンパイルエラーを回避するため。
Headerの中を囲ってますがもちろんincludeしているC++側で囲ってもOKです。
ただ、Header側で囲ってあげるのがやさしさですよね。
自分で書いたときは気づきますが、人からライブラリをもらったときは
たまに"あれっ"てなるときあります。


リンクの順序


リンクには実は順番がありますという話。
以下のようなコードがあったとします。
test.c

#include "liba.h"
int main(){
    func_lib_a();
    return 0;
}

liba.c
#include <stdio.h>
#include "liba.h"

void func_lib_a()
{
    func_lib_b();
}

libb.c
#include <stdio.h>
#include "libb.h"

void func_lib_b()
{
    printf("lib b called.\n");
}

Headerはプロトタイプ宣言だけなので省略
また、liba.cとlib.cは静的ライブラリとしてコンパイルされているとします。

つまりは、静的ライブラリAが静的ライブラリBの関数を参照しており、
main文が含まれるtest.cからライブラリAの関数を呼び出した状態です。

数珠つなぎにリンクされるので、当然両方をリンクする必要があります。
なので、両方をリンカに渡してやると。

$ gcc -o test test.c -L. -lb -la
./liba.a(liba.o): In function `func_lib_a':
liba.c:(.text+0x13): undefined reference to `func_lib_b'
collect2: ld はステータス 1 で終了しました

となり、リンクエラーとなります。
一方
$ gcc -o test test.c -L. -la -lb

は成功します。
一般的なUNIX系のオプションは並び順を問われないことが多いので忘れがちですが、
リンカは順番にシンボル解決が行われるため、順番が重要になります。
参照されるものは後です。

0 件のコメント:

コメントを投稿