気の向くままに辿るIT/ICT/IoT
UNIX/Linux

UNIX/Linux gccによるC共有ライブラリの作成

ホーム前へ次へ
共有ライブラリ(.so)って作れるの?

gccで共有ライブラリ(.so)を作成

gcc

 gccとはbash同様FSFのGNUプロジェクトによって開発されたコンパイラでGNU C Compiler、後にC/C++/Objective C/FORTRAN/Java...といった各種プログラミング言語に対応したことからGNU Compiler Collectionとして知られています。

コンパイルの過程

 コンパイラとコンパイルではUNIX/Linux及びシェル環境におけるコンパイラとコンパイルについてccとgccの関係、続くUNIX/Linux C コンパイル過程では、実行ファイルができるまでに具体的に何が行われているのかについて、その流れを追いました。

 そして実際にgccでコンパイルと中間ファイルの残し方、更にstatic library/スタティックライブラリ/静的ライブラリ(拡張子.a/archive/アーカイブ)ファイルの作成とリンクによる実行ファイルの作成をしてみました。

gccで共有ライブラリ(.so)を作成

$ ls 

foo.c foo.o bar.c bar.o 

$ gcc -Wall -shared -o libfoob.so foo.o bar.o 

$ ls 

foo.c foo.o bar.c bar.o libfoob.so* 

 ここではgccでlibfoob.soという共有ライブラリ(拡張子.so/shared object/共有オブジェクト)を作成しますが、その為には、gccにsharedオプションを渡してコンパイルします。

 静的ライブラリlibfoob.a同様、この共有ライブラリの名前は任意のfoob(C言語標準共有ライブラリはlibc.soの名前もlibに.soという拡張子の付いたcという名前)です。

$ ls -F 

foo.c foo.o bar.c bar.o libfoob.so* 

hoge.c hoge.o 

$ gcc -o hoge hoge.o 

$ ls -F 

foo.c foo.o bar.c bar.o libfoob.so* 

hoge* hoge.c hoge.o 

 このlibfoob.soを実行時にリンクする実行ファイルがあるものとして架空のボディファイルhoge.cをコンパイルして作っておいたという筋書きになっているhoge.oを使ってコンパイルします。

 これも静的ライブラリlibfoob.a同様ですが、この例では共有ライブラリlibfoob.soというライブラリの検索パスにカレントディレクトリを指定しないとエラーになりますが、実行の度に動的にリンクされるので静的ライブラリlibfoob.aの時のような一時的なコンパイルオプションによるパス設定ではなく環境変数を利用します。

$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH 

 Cで使われる共有ライブラリ用の環境変数はLD_LIBRARY_PATHでbashでは環境変数とイコール、そのあとに続くこの例では1つのピリオド(カレントディレクトリ)の間に空白を入れずにセットします。

 環境変数のセパレータはUNIX/Linuxではコロン : (、Windowsはセミコロン ; )なので、この例ではテストの為にカレントディレクトリに追加したい共有ライブラリがあるものとして、その後にコロンに続けて既存のLD_LIBRARY_PATHを改めて追加しています。

$ ./hoge 

 これでhogeを実行してエラーがなければ共有ライブラリlibfoob.soをリンクして実行できたことになります。

動的リンク方法

 尚、UNIX/LinuxではPIC/Position-Independent Code/位置独立コードという動的にリンクされるライブラリが存在する場所を特定せずに利用できる仕組みがあります。

UNIX/Linuxの共有ライブラリのサーチパス

 SolarisやBSD系といったPC UNIXでは、/etc/rc.conf、Linuxでは、/etc/ld.so.confで指定されたパスを共有ライブラリのパスとして検索します。

UNIX

 UNIXにおいて/etc/rc.confが既に存在する場合のデフォルト設定は/etc/defaults/rc.confからコピーされたものだと思われ(、先行して/etc/defaults/rc.conf、その後/etc/rc.confと両方とも読み込まれるようですが)、仮に編集の必要があって/etc/rc.confが存在しない場合でも/etc/defaults/rc.confを/etc/rc.confとしてコピーして編集、OSを再起動(OSやバージョンによっては当該サービスやデーモン、スクリプトをrestart/reload/condrestartなど)すると設定が反映されます。

Linux

 Linuxにおいては、ディストリビューションによって/etc/ld.so.confに共有ライブラリのパスが(個々に)記述されていたり、[include /etc/rc.d/*.conf]のようにワイルドカード指定した各種[*.conf]ファイルのあるディレクトリをincludeしていたりすることがあり、編集する場合、前者はパスを追記、後者は当該ディレクトリに必要となる[.conf]ファイルを追加し、rootでldconfigコマンドを実行し設定を有効にします。

Windowsの共有ライブラリのサーチパス

 Windowsではレジストリを利用し、これに当たるDLL/Dynamic Link Library/ダイナミックリンクライブラリを(アプリケーションのあるディレクトリを個々に指定するなどして)特定の場所に置いて利用しているようです。

 UNIX/Linuxでもその歴史の中でRelocatable Object Code/再配置オブジェクトコードやAbsolute Object Code/絶対オブジェクトコードなど一長一短を抱えながらも複数のリンク方法が検討、並行して採用されてきた結果としてPIC方式をもサポートしているという経緯の中においても前述のようにライブラリの存在場所(位置情報)として環境変数LD_LIBRARY_PATHにライブラリパスを持たせる方法は、PIC方式をその仕組みに持つUNIX/Linuxらしからぬ手法とも言えますが、コンパイル時にPIC形式でリンクすることももちろんできます。

gcc共有ライブラリ用フラグ-fpic/-fPIC

$ gcc -fpic -c -Wall  foo.c 

$ gcc -fPIC -c -Wall  foo.c 

 共有ライブラリがどこにあっても利用できるようにする為に、gccには共有ライブラリ用フラグとして-fpicと-fPICがありますが、いずれもUNIX/Linuxの種類など環境に依存するので確実に利用できる場合にのみこれらのオプションを使うべきとされ、-fpicと-fPICは共にマクロになっており、定義されていれば、マクロ __pic__and__PIC__ の値が-fpicは1、-fPICは2にセットされているようです(http://gcc.gnu.org/)。

$ gcc -fpic -c -Wall foo.c 

$ gcc -shared -Wl,-soname,libfoob.so.1 -o libfoob.so.1.0 foo.o -L. 

 -fpicと-fPICの違いは、-fpicはオーバーヘッドが小さく実行速度も比較的高速である一方、OSによってはサイズに制約を持つものも存在し、その場合-fPICを利用できる場合は、これが回避できるとされています。

$ ln -s libfoob.so.1 libfoob.so 

 この例は、カンマ区切りでリンカオプション-sonameとその引数となるファイル名libfoob.so.1をリンカに渡す旨の-Wlオプション付きでlibfoob.so.1.0という共有ライブラリを生成(-shared)しますが、リンカはlibfoob.soという共有ライブラリを探すのでlibfoob.soがlibfoob.so.1を指すシンボリックリンクとなるようにリンクを作成する(lnコマンドを使用する)必要があります。

 尚、この例のライブラリ名の1.0の1はメジャーバージョン、0はマイナーバージョン(旧タイプのUNIXでは更にドットに続けてリビジョンがあった時代もあった)で共有ライブラリとして認識されるlibfoob.soというメジャーバージョンlibfoob.so.1をリリースするに当たり、以後マイナーバージョンをリリースする可能性があるのでローカルでは1.9まであるかもしれない最初のバージョンlibfoob.so.1.0として出力しておき、リンカに渡すメジャーバージョンとしては(1.9までは)libfoob.so.1と命名し、libfoob.soからのシムリンクによりリンカによってlibfoob.soが探索されるとlibfoob.so.1が参照されるという想定です。

静的ライブラリ(.a)と共有ライブラリ(.so)

 尚、コンパイル時にリンクするライブラリを指定する状況において同名の静的ライブラリ(.a)と共有ライブラリ(.so)が同じ参照パスにあった場合、gccは共有ライブラリを優先します(/usr/lib内のlibc.aとlibc.soも同様)。

 つまり、-l(小文字のエル)オプションによって探索するライブラリ名を指定した場合、デフォルト(既定)では、共有ライブラリを検索してリンク、万一共有ライブラリが存在しない場合にのみ静的ライブラリを検索し、あればリンクします。

 これを変更するにはコンパイル時に別途オプションを指定する必要があります。

 共有(ダイナミック)ライブラリをリンクする場合 

 -Bdynamic, -dy, -call_shared 

 共有(ダイナミック)ライブラリをリンクしない場合 

 -Bstatic, -dn, -non_shared, -static 

 環境にもよると思いますが、静的ライブラリ(.a)と共有(ダイナミック)ライブラリ(.so)のリンクに関するリンカオプションには、ダイナミックライブラリをリンクするか否かを指定する為に若干ニュアンスの異なるものを含むいくつかのフラグがあります。

 ちなみに-Bdynamic/-Bstatic、-dy/-dnは、それぞれ-Bオプションにdynamic/static、-dオプションにy/n(yes/no)を付加して指定するものです。

$ cc -o prog main.o file1.c -Bstatic -lfoo -Bdynamic -lbar 

 リンカーとライブラリ/sun(oracle) 

 また、-Bdynamic/-Bstatic(や、おそらく-call_shared/-non_shared)は、1回のリンクで名前の異なる別個の静的ライブラリと共有ライブラリを同時に指定する場合、-dy/-dnは、排他的、つまり、共有(ダイナミック)ライブラリをリンクするか否かの二者択一の場合、-staticは、探索パスから静的ライブラリのみ検索する場合に指定するものと考えてよいと思います。

 よって-dy/-dnについては、二者択一でデフォルトでは共有ライブラリが検索されることから-dyは省略可能ですし、ダイナミックリンクのみリンク指定するケースにおいては、-Bdynamicや-call_sharedも敢えて指定する必要はない(省略可能)ということになりますが、敢えて指定した場合も省略した場合も共有ライブラリがない場合に限って同名の静的ライブラリを検索するという挙動は同様です。

 言い換えれば、これらのオプションは静的ライブラリ(アーカイブ)だけリンクしたい、または加えて静的ライブラリ(アーカイブ)もリンクしたい場合に指定するもので、基本的に静的ライブラリ主体のオプションであると言っても良いかと思います。

オブジェクトファイルとその他中間ファイルの有用性

 静的ライブラリ(.a)と共有ライブラリ(.so)の構成ファイルにもオブジェクトファイルが利用され、個々のソースファイルのコンパイルも数が増える程オブジェクトファイルを残しておく方が時間短縮になることが何となく実感できたでしょうか。

 実際には開発作業がコンパイルだけで済むケースは稀でdbx/gdbによるデバッグも欠かせないでしょうし、C言語やシェルスクリプトによるものを含め、開発中の当該プログラムをコールする呼び出し元プログラムを想定したドライバや開発中の当該プログラムからコールするプログラムを想定したスタブでのテストも必要でしょう。

 そうした作業の中でオブジェクトファイル以外のプリプロセス済みソースファイル(.i)やアセンブリコードファイル(.sや.S)も有用な検証物の1つと成り得ることもあるでしょう。

リビジョン管理ツールやmakefile

 厳密にはgccだけの話ではありませんが、ファイル数が増えてくると、コンパイル作業もかなり疲労困憊する(「困憊するコンパイル」を3回も言ってみれば更に疲労困憊する!?)作業です。

 gccはファイルさえ渡せば、更にいくつかオプションを付加すれば、かなりのことをやってのけてくれますが、ボディファイルとオブジェクトファイル、ヘッダファイル、各関係するソースファイル・・・という中でどれをいつコンパイルしたか管理するのはかなり無理があります。

 そんな時に有用なのが似て非なる目的や特徴を持つRCSなどのリビジョン管理ツールやmakeコマンドとmakefile(Makefile)です。

 リビジョン管理ツールとは、往々にして1人、更にはより効果的な複数人で作業するソースの世代管理をしてくれるツールです。

 ホテルや飛行機並みにチェックイン/チェックアウトというソースファイル様の入退場をご確認させて頂く作業により同一ソースの重複作業を防止できたり、自動的にバックアップファイルを作成する(・しないの)設定ができたり、万一の際、そのバックアップからファイルを復旧させることができたりするツールです。

makefile

 makefile/Makefileを作成してでできることはアプリケーションインストール時の環境構築やソースのコンパイルだけではありませんが、コンパイル作業においては、ルールと依存関係というキーワードと元のファイルと作成されるファイルの更新日付を比較するということをベースにして元の(場合によっては複数のいずれかの)ファイルが更新された場合にだけコンパイルしたり、makefileで他のmakefileを走らせ結果的に大量のmakefileを実行することもでき、一度書きあげればmakeコマンドを走らせるだけで多くの仕事をこなしてくれるかなり強力なツールであり、とりとめのない非効率且つ精神衛生上よくない作業を省き単独でgccを利用する以上に時間短縮に貢献するので小規模から大規模まで使わずにいるのはとてももったいないツールです。

 尤もツールと言ってもテキストファイルに決まったルールに沿った記述をするだけの手軽さで実現できる機能は無限大-20000くらい。。。って結局無限大!?

LINK

ホーム前へ次へ