2005年10月10日

リンクと同名のシンボル

C や C++ のプログラムで同じ名前のグローバルなシンボルが 2つ以上存在するとどうなるでしょうか。 Debian GNU/Linux sarge + GCC 3.3.5 での動作を見てみます。

 

静的リンクの場合

まず、次のようなファイル a.c があります。 a.c ではグローバルな関数 func() を定義しています。

#include <stdio.h>
void func() {
    printf("func() in a.c\n");
}

次に、 b.c でも同様に func() を定義しています。a.c のものとよく似ていますが、 printf で表示されるメッセージは異なります。

#include <stdio.h>
void func() {
    printf("func() in b.c\n");
}

最後に、 main.c では func() を呼び出しています。

void func();
int main () {
    func();
    return 0;
}

これらの 3つのファイルをそれぞれコンパイルして静的にリンクしようとすると、func() が複数あるため、リンカがエラーを検出します。このエラーのおかげで、予期せぬ func() が呼ばれるという事態を避けられます。

% gcc -c a.c
% gcc -c b.c
% gcc -c main.c
% gcc -o main a.o b.o main.o
b.o(.text+0x0): In function `func':
: multiple definition of `func'
a.o(.text+0x0): first defined here
collect2: ld returned 1 exit status

動的リンクの場合

同じソースコードをそれぞれ動的共有オブジェクト (.so ファイル) としてコンパイル・リンクします。すると今回はエラーは発生しません。

% gcc -fPIC -shared -o a.so a.c
% gcc -fPIC -shared -o b.so b.c
% gcc -fPIC -shared -o main.so main.c
% gcc -o main-shared ./a.so ./b.so ./main.so

実行すると、 a.so の中の func() が呼び出されます。これは、main-shared の実行時に行われる動的リンクの際に、 a.so 内の func() が b.so のものより先に見つかるからです。

% ./main-shared
func() in a.c

リンク時のコマンドライン引数の a.so と b.so の順序を逆にすると、b.so の func() が先に見つかるようになります。

% gcc -o main-shared2 ./b.so ./a.so ./main.so
% ./main-shared2
func() in b.c

動的リンクの際には、基本的に初めに見つかったシンボルの定義が使われます。GNU C Library の主要開発メンバーである Ulrich Drepper氏による How to Write Shared Libraries に次のような記述があります。

Note that there is no problem if the scope contains more than one definition of the same symbol. The symbol lookup algorithm simply picks up the first definition it finds. ... This concept is very powerful ... One example for this mechanism is the use of the LD_PRELOAD functionality ...

スコープ内に同じシンボルの定義が 2つ以上含まれていても構わない。シンボルルックアップのアルゴリズムは単純に最初に見つかった定義を拾うだけだ。 … このコンセプトは非常に強力であり … その一例は LD_PRELOAD の機能の利用である …

LD_PRELOAD

LD_PRELOAD の実験のために、次のようなファイル c.c を作ります。

#include <stdio.h>
void func() {
    printf("func() in c.c\n");
}

それから、 c.so を作成して、環境変数 LD_PRELOAD=./c.so をセットした上で、さきほどの main-shared を実行すると、 c.so 内の func() が実行されます。

% gcc -fPIC -shared -o c.so c.c
% LD_PRELOAD=./c.so ./main-shared
func() in c.c

まとめ

このように、動的共有オブジェクトを使ったプログラムでは、別々の .so ファイルに同名のシンボルの定義が含まれていてもプログラムは正常にリンク・実行できます。この動作は便利でもある反面、予期せぬ挙動が起きる可能性があるため注意が必要です。C++の話へと続く…

この辺りの話は Linkers & Loaders が参考になります。 英語版 はウェブで読めます。