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の開発環境において、ライブラリの外に公開するシンボルを制限する方法を紹介しました。共有ライブラリを開発する上で役立つノウハウではないかと思います。