2006年2月22日
ライブラリの外に公開するシンボルを制限する
C言語にはファイル内 (コンパイル単位) からしかアクセスできない static 関数と、別のファイルからもアクセスできる非static 関数があります。しかし、ライブラリを作成する上では、この2つのスコープだけでは不十分なときがあります。
本記事では GNUの開発環境において、ライブラリの外に公開するシンボルを制限する方法を紹介します。
次のような例を考えてみます。
% cat a.c // foo() は libfoo の主役の関数なので公開したい void foo() { bar(); } % cat b.c // bar() はライブラリの中だけで使われるべきなので本当は公開 // したくない。しかし別のファイルに含まれる foo() から使われ // ているので、非staticにせざるをえない void bar() { }
このようなコード a.c と b.c をそれぞれコンパイル・リンクして libfoo.so を作ると、通常、foo() と bar() の両方の関数のシンボルがライブラリの外に公開されます。しかし、本来 bar() は外には公開したくない関数です。
% gcc -c a.c; gcc -c b.c; gcc -shared -o libfoo.so a.o b.o % nm -D libfoo.so |grep -v '_' 000005e4 T bar 000005d4 T foo
そこで、GNUリンカのバージョンスクリプトを用いると、外に公開する関数を制限できます。下の例では foo をグローバル (ライブラリの外に公開) に、それ以外をローカル (ライブラリの中に閉じる) と定義しています。
% cat libfoo.map { global: foo; local: *; };
このようなバージョンスクリプト libfoo.map を gcc に -Wl,--version-script,libfoo.map で渡してリンクすると、bar は隠れて foo だけがライブラリの外に公開されます。-Wl はカンマで区切られたパラメータをリンカに渡すというオプションです。
% gcc -c a.c; gcc -c b.c; gcc -shared -o libfoo.so a.o b.o \ -Wl,--version-script,libfoo.map % nm -D libfoo.so |grep -v '_' 000004d8 T foo
メリット
公開するシンボルを制限することには次のようなメリットがあります。
- 非公開APIをライブラリの利用者に見せない
- 共有ライブラリ内のシンボルテーブルを小さくし、動的リンクのコストを軽減する
動的リンクのコストは小さなソフトウェアではほとんど無視できますが、Firefox や OpenOffice.org といった巨大なソフトウェアでは大きな問題になります (prelink の効果を測定するを参照)。実際、これらのソフトウェアのビルドではバージョンスクリプトが用いられています。
まとめ
GNUの開発環境において、ライブラリの外に公開するシンボルを制限する方法を紹介しました。共有ライブラリを開発する上で役立つノウハウではないかと思います。