2005年10月12日

リンクと同名シンボル: weak シンボル編

先日の記事では静的リンクの際に同名シンボルの衝突は検出されると書きました。しかし、 weak シンボルが存在すると話は変わります。

 

次のようなプログラム main.cpp があります。

#include <iostream>
class Foo {
public:
    Foo(int x) : x_(x) {}
    void func() {
        std::cout << x_ << std::endl;
    }
private:
    int x_;
};

int main () {
    Foo foo(256);
    foo.func();
    return 0;
}

このプログラムをコンパイルして実行すると 256 と表示されます。

% g++ -c main.cpp
% g++ -o main main.o
% ./main
256

ここで、main.cpp とはまったく独立に開発された次のような a.cpp が存在します。a.cpp 内でもクラス Foo を実装しています。

#include <iostream>
class Foo {
public:
    Foo(int x);
private:
    int x_;
};

Foo::Foo(int x) : x_(x * x) {
}

そして、 a.cpp をコンパイルした a.o が運悪く main にリンクされると大変厄介なことが起きます。

% g++ -c a.cpp
% g++ -o main main.o a.o
% ./main
65536

今回の実行結果は 65536 です。 256 ではなく、その累乗が表示されてしまいました。このとき、リンクの順は main.o, a.o でも a.o, main.o でもどちらでも結果は変わりません。

main.cpp 内で定義されたコンストラクタ Foo::Foo(int) ではなく a.cpp 内の Foo::Foo(int) が使われていることは明らかですが、なぜこのようなことが起きるのでしょうか。

weak シンボル

原因は、 main.o に含まれる Foo::Foo(int) が weak シンボルであり、 a.o に含まれる Foo::Foo(int) が非 weak シンボルであることにあります。以下の nm の出力で W と表示されているのが weak シンボルの印です。

% nm --demangle main.o | grep Foo::
00000000 W Foo::func()
00000000 W Foo::Foo(int)
% nm --demangle a.o | grep Foo::
00000012 T Foo::Foo(int)
00000000 T Foo::Foo(int)

weak シンボルについては Linkers & Loaders の6章に以下の解説があります。

ELF では、weak参照のほかに weak定義というもう1つの weakシンボルを追加している。 weak 定義は、通常の定義が存在しない場合に大域シンボルを定義する。通常の定義が存在する場合は、 weak 定義は無視される。

つまり、 a.o に通常の (非 weak の) 定義が存在するために main.o の Foo::Foo(int) の weak 定義は無視されているということです。

ひとまず、この問題を回避するには、無名 namespace を用いてリンケージリークを防ぐのが効果的です。具体的には main.cpp の class Foo { ... } の周りを namespace { ... } で囲みます。

weak 定義と重複コードの除去

ところで、なぜ main.o の Foo::Foo(int) および Foo::func() は weak 定義になっているのでしょうか。これは、 g++ は (少なくとも 3.3.5 は) クラス定義内の関数定義を weak 定義としてコンパイルするためです。また、この weak 定義は .o ファイル内の .gnu.linkonce.t.* という名前のセクションに配置されます。

クラス定義は多くの場合、 .h ファイル内に記述され、 .h ファイルは複数の .cpp ファイルに #include されます。そのため、クラス定義内の関数定義を普通にコンパイルすると、多数の .o ファイルごとに同じ関数の非 weak 定義が含まれることになり、リンク時に衝突します。

おそらく、 g++ はこの問題を避けるためにクラス定義内の関数定義を weak 定義としてコンパイルしているのではないかと思います。 .gnu.linkonce.t.* はリンク時の重複コードの除去に使われるセクションです。同名の weak 定義が複数存在する場合にはリンク時に 1つにまとめられます。

まとめ

前々回前回、 今回と 3回にわたって「リンクと同名のシンボル」というテーマで書きました。もともと書きたかったのは今回の weak シンボルの話だったのですが、順を追って書いていったら長くなりました。

なお、一連の記事はすべて Debian GNU/Linux sarge + GCC 3.3.5 で試しました。将来的には動作が変わっている可能性があるのでご注意ください。他のOSでも動作が異なる点が多いのではないかと思います。