2006年2月19日

リンクされているライブラリによってプログラムの動作を変える

weak シンボルを用いると、リンクされているライブラリによってプログラムの動作を変えることができます。ここでは GNU 拡張を用いて weak シンボルを利用する方法を紹介します。

 

それではさっそくサンプルコードを見てみましょう。このプログラムでは、libm に含まれる sqrt() 関数があるときは利用し、ない場合はその旨のメッセージを表示します。

weak.c

#include <stdio.h>
extern double sqrt(double x) __attribute__ ((weak));

void
func ()
{
    if (sqrt) {
        printf("%f\n", sqrt(10.0));
    } else {
        printf("sqrt isn't available\n");
    }
}

weak-main.c

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

実行結果は次の通りです。

% gcc -fPIC -shared -o weak.so weak.c  # weak.so を作成する

% gcc weak-main.c ./weak.so -lm; ./a.out
3.162278

% gcc weak-main.c ./weak.so; ./a.out
sqrt isn't available

# LD_PRELOAD でも OK
% gcc weak-main.c ./weak.so; LD_PRELOAD=libm.so ./a.out
3.162278

このプログラムのポイントは weak.c の 2行目の部分です。 __attribute__ ((weak)) というGNU の拡張を使って weak シンボルを宣言しています。

weak.so に含まれるシンボルを nm で見ると、 sqrt は weak シンボルになっています。

% nm weak.so |grep sqrt
         w sqrt

weak シンボルは、シンボルの定義 (関数の実体など) がリンク時に見つからなかった場合、0 に初期化されます。よって上のプログラムでは、sqrt 関数がリンク時に見つからないときは sqrt = 0 となり、if (sqrt) の else のブロックが実行されます。

weakシンボルのメリット

上の例と似たようなことは #ifdef HAVE_SQRT のようなマクロでもできます。

#ifdef HAVE_SQRT
        printf("%f\n", sqrt(10.0));
#elif
        printf("sqrt isn't available\n");
#endif

しかし、この場合、weak.so の動作はコンパイル時に固定されるという違いがあります。このため、 weak.so が HAVE_SQRT = 1 でコンパイルされた場合、 weak.so を利用するプログラム (クライアント) は sqrt() の呼び出しのために、必ず libm.so をリンクする必要が生じます。

libm のような基本的なライブラリの場合は問題はほとんどないと思いますが、特殊なライブラリの場合 (一般的でないライブラリや libpthread のようにプログラムの性能に影響が生じるライブラリなど)、クライアントはそのライブラリをリンクするか選択したい場合もあります。weak シンボルの手法が役立つのはこのような場合です。

まとめ

weak シンボルを用いて、リンクされているライブラリによってプログラムの動作を変える方法を紹介しました。GNU 拡張を用いるため移植性が下がってしまうのが難点ですが、ライブラリの依存関係の柔軟性を高めるのに役立つと思います。