この章では、ライブラリの構築方法を説明します。
ライブラリには 2 つの利点があります。まず、ライブラリを使えば、コードをいくつかのアプリケーションで共有できます。共有したいコードがある場合は、そのコードを含むライブラリを作成し、コードを必要とするアプリケーションとリンクできます。次に、ライブラリを使えば、非常に大きなアプリケーションの複雑さを軽減できます。アプリケーションの中の、比較的独立した部分をライブラリとして構築および保守することで、プログラマは他の部分の作業により専念できるようになるためです。
ライブラリの構築とは、.o ファイルを作成し (コードを -c オプションでコンパイルし)、これらの .o ファイルを CC コマンドでライブラリに結合することです。ライブラリには、静的 (アーカイブ) ライブラリと動的 (共有) ライブラリがあります。
静的 (アーカイブ) ライブラリの場合は、ライブラリのオブジェクトがリンク時にプログラムの実行可能ファイルにリンクされます。アプリケーションにとって必要な .o ファイルだけがライブラリから実行可能ファイルにリンクされます。静的 (アーカイブ) ライブラリの名前には、通常、接尾辞.a が付きます。
動的 (共有) ライブラリの場合は、ライブラリのオブジェクトはプログラムの実行可能ファイルにリンクされません。その代わりに、プログラムがこのライブラリに依存することをリンカーが実行可能ファイルに記録します。プログラムが実行されるとき、システムは、プログラムに必要な動的ライブラリを読み込みます。同じ動的ライブラリを使用する 2 つのプログラムが同時に実行されると、ライブラリはこれらのプログラムによって共有されます。動的 (共有) ライブラリの名前には、接尾辞として通常 .so か、 .so.<番号> が付きます。<番号> はバージョン番号を示します。
共有ライブラリを動的にリンクすることは、アーカイブライブラリを静的にリンクすることに比べていくつかの利点があります。
実行可能ファイルのサイズが小さくなる
実行時にコードのかなりの部分をプログラム間で共有できるため、メモリーの使用量が少なくなる
ライブラリを実行時に置き換える場合でも、アプリケーションとリンクし直す必要がない (プログラムの再リンクや再配布をしなくても、Solaris 環境でプログラムが新しい機能を使用できるのは、主にこの仕組みのためです)
dlopen() 関数呼び出しを使えば、共有ライブラリを実行時に読み込むことが できる
ただし、動的ライブラリには短所もあります。
実行時のリンクに時間がかかる
動的ライブラリを使用するプログラムを配布する場合には、それらのライブラリも同時に配布しなければならないことがある
共有ライブラリを別の場所に移動すると、システムがライブラリを検索できずに、プログラムを実行できなくなることがある (環境変数 LD_LIBRARY_PATH を使えば、この問題は解決できます)
静的 (アーカイブ) ライブラリを構築する仕組みは、実行可能ファイルを構築することに似ています。一連のオブジェクト (.o) ファイルは、CC で -xar オプションを使うことで 1 つのライブラリに結合できます。
静的 (アーカイブ) ライブラリを構築する場合は、ar コマンドを直接使用せずに CC -xar を使用してください。C++ 言語では一般に、従来の .o ファイルに収容できる情報より多くの情報 (特に、テンプレートインスタンス) をコンパイラが持たなければなりません。-xar オプションを使用すると、テンプレートインスタンスを含め、すべての必要な情報がライブラリに組み込まれます。make ではどのテンプレートファイルが実際に作成され、参照されているのかがわからないため、通常のプログラミング環境でこのようにすることは困難です。CC -xar を指定しないと、参照に必要なテンプレートインスタンスがライブラリに組み込まれないことがあります。構築の例を次に示します。
% CC -c foo.cc # mainを含むファイルをコンパイルし、テンプレートオブジェクトを作成する % CC -xar -o foo.a foo.o # すべてのオブジェクトを1つのライブラリに集める
-xar フラグによって、CC が静的 (アーカイブ) ライブラリを作成します。-o 命令は、新しく作成するライブラリの名前を指定するために必要です。コンパイラは、コマンド行のオブジェクトファイルを調べ、これらのオブジェクトファイルと、テンプレートレポジトリで認識されているオブジェクトファイルとを相互参照します。そして、ユーザーのオブジェクトファイルに必要なテンプレートを (本体のオブジェクトファイルとともに) アーカイブに追加します。-xar フラグは既存のアーカイブの作成や更新のためのもので、保守には使用できません。これは ar -cr を実行するのと同じことです。
1 つの .o ファイルには 1 つの関数を入れることをお勧めします。アーカイブとリンクする場合、特定の .o ファイルのシンボルが必要になると、.o ファイル全体がアーカイブからアプリケーションにリンクされます。.o ファイルに 1 つの関数を入れておけば、アプリケーションにとって必要なシンボルだけがアーカイブからリンクされます。
動的 (共有) ライブラリの構築方法は、コマンド行に -xar の代わりに -G を指定することを除けば、静的 (アーカイブ) ライブラリの場合と同じです。
ld は直接使用しないでください。静的ライブラリの場合と同じように、CC コマンドを使用すると、必要なすべてのテンプレートインスタンスがテンプレートレポジトリからライブラリに組み込まれます (テンプレートを使用している場合)。さらに、CC コンパイラは、大域変数がライブラリに定義されている場合、動的ライブラリが正しく構築されていないと大域変数を初期化しません。すべての静的コンストラクタとデストラクタはそれぞれ .init と .fini セクションから呼び出されます。アプリケーションにリンクされている動的ライブラリのすべての静的コンストラクタは、main() の実行より「前に」呼び出されます。CC -G コマンドを使用して動的ライブラリを構築しないと、例外が機能しないことがあります。
動的 (共有) ライブラリを構築するには、CC の -Kpic や -KPIC オプションで各オブジェクトをコンパイルして、再配置可能なオブジェクトファイルを作成する必要があります。次に、これらの再配置可能オブジェクトファイルから動的ライブラリを構築します。原因不明のリンクエラーがいくつも出る場合は、-Kpic や -KPIC でコンパイルしていないオブジェクトがある可能性があります。
ソースファイル lsrc1.cc と lsrc2.cc から作成するオブジェクトファイルから C++ 動的ライブラリ libgoo.so.1 を構築するには、次のようにします。
demo% CC -G -o libfoo.so.1 -h libfoo.so.1 -Kpic lsrc1.cc lsrc2.cc
-G オプションは動的ライブラリの構築を指定し、-o オプションはライブラリのファイル名を指定します。-h オプションは、共有ライブラリの名前を指定しています。-Kpic オプションは、オブジェクトファイルが位置に依存しないことを指定しています。
dlopen() で共有ライブラリを開く場合、例外が機能するようにするには、RTLD_GLOBAL を使用する必要があります。
例外を含む共有ライブラリを構築するとき、ld に-Bsymbolic オプションを渡さないでください。必要な例外が捕獲されなくなる可能性があります。
ある組織の内部でしか使用しないライブラリを構築する場合には、一般的な使用には適さないオプションを使ってライブラリを構築することもできます。具体的には、ライブラリはシステムのアプリケーションバイナリインタフェース (ABI) に準拠していなくてもかまいません。たとえば、ライブラリを -fast オプションでコンパイルして、特定のアーキテクチャ上でのパフォーマンスを向上させることができます。同じように、-xregs=float オプションでコンパイルして、パフォーマンスを向上させることもできます。
他の組織からも使用できるライブラリを構築する場合は、ライブラリの管理やプラットフォームの汎用性などの問題が重要になります。ライブラリを公開にするかどうかを決める簡単な基準は、アプリケーションのプログラマがライブラリを簡単に再コンパイルできるかどうかということです。公開ライブラリは、システムの ABI に準拠して構築しなければなりません。一般に、これはプロセッサ固有のオプションを使用しないということを意味します (たとえば、-fast や -xtarget は使用しないなど)。
SPARC ABI では、いくつかのレジスタがアプリケーション専用で使用されます。V7 と V8 では、これらのレジスタは %g2、%g3、%g4 です。V9 では、これらのレジスタは %g2 と %g3 です。ほとんどのコンパイルはアプリケーション用に行われるので、C++ コンパイラは、デフォルトでこれらのレジスタを一時レジスタに使用して、プログラムのパフォーマンスを向上しようとします。しかし、公開ライブラリでこれらのレジスタを使用することは、SPARC ABI に適合しないことになります。公開ライブラリを構築するときには、アプリケーションレジスタを使用しないようにするために、すべてのオブジェクトを -xregs=no%app1 オプションでコンパイルしてください。
C++ で作成されたライブラリを C プログラムから使用できるようにするには、C API を作成する必要があります。そのためには、エクスポートされるすべての関数を extern C にします。ただし、これができるのは大域関数だけで、メンバー関数にはできません。
さらに、C++ 実行時ライブラリにもまったく依存しないようにするには、ライブラリソースに対して次のコーディング規則を適用する必要があります。
独自の大域演算子 new や delete を定義する場合を除き、new や delete を 使用しない
配列の new や delete を使用しない
例外を使用しない
RTTI を使用しない
C プログラムから dlopen で C++ 共有ライブラリにアクセスする場合は、共有ライブラリが適切な C++ 実行時ライブラリ (-compat=4 の場合は libC.so.5、-compat=5 の場合は libCrun.so.1) に依存していなければなりません。
そのためには、共有ライブラリを構築するときに、-compat=4 の場合は -lc、-compat=5 の場合は -lCrun を次のようにコマンド行に追加します。
demo% CC -G -compat=4 ... -lC demo% CC -G -compat=5 ... -lCrun
共有ライブラリが例外を使用している場合には、ライブラリが C++ 共有ライブラリに依存していないと、C プログラムが正しく動作しないことがあります。
共有ライブラリを dlopen で開く場合は、RTLD_GLOBAL を使用しないと、例外は機能しません。
C++ コンパイラに添付されたすべてのライブラリはマルチスレッドに対して安全です。マルチスレッド化アプリケーションを構築したり、アプリケーションにマルチスレッド化ライブラリをリンクする場合は、プログラムを -mt オプションでコンパイルおよびリンクする必要があります。このオプションを指定すると、-D_REENTRANT がプリプロセッサに渡され、-lthread が正しい順序で ld に渡されます。-compat=4 の場合は、-mt オプションを指定すると、libthread が libC より前にリンクされます。アプリケーションを直接 -lthread とリンクすると、libthread が正しくない順序でリンクされる恐れがあります。
次の例は、マルチスレッドアプリケーションの正しい構築方法を示します。
demo% CC -c -mt myprog.cc demo% CC -mt myprog.o
次の例は、マルチスレッドアプリケーションの正しくない構築方法を示します。
demo% CC -c -mt myprog.o demo% CC myprog.o -lthread
アプリケーションに libthread がリンクされているかどうかは、ldd コマンドを使用して調べます。
demo% CC -mt myprog.cc demo% ldd a.out libm.so.1 => /usr/lib/libm.so.1 libCrun.so.1 => /usr/lib/libCrun.so.1 libw.so.1 => /usr/lib/libw.so.1 libthread.so.1 => /usr/lib/libthread.so.1 libc.so.1 => /usr/lib/libc.so.1 libdl.so.1 => /usr/lib/libdl.so.1
C++ サポートライブラリ libCrun、libiostream、libCstd、libC はマルチスレッドに対して安全ですが、async に対しては安全ではありません。つまり、マルチスレッド化アプリケーションでは、サポートライブラリの関数をシグナルハンドラで使用することはできません。使用すると、デッドロック状態になることがあります。
マルチスレッドアプリケーションでは、次の機能をシグナルハンドラで使用すると問題が起こる可能性があります。
iostream
new と delete
例外