C 言語で書かれた静的ライブラリと共有ライブラリについて、いまいち理解がちゃんとしていなかったのでまとめておく。 ライブラリというのは、複数のアプリケーションで使われるような共通の機能をまとめたものをいう。
今回使った環境は次の通り
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.04 DISTRIB_CODENAME=focal DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS" $ uname -rm 5.11.0-1021-gcp x86_64 $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ ar --version GNU ar (GNU Binutils for Ubuntu) 2.34 Copyright (C) 2020 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or (at your option) any later version. This program has absolutely no warranty. $ ldd --version ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31 Copyright (C) 2020 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Written by Roland McGrath and Ulrich Drepper.
もくじ
下準備
まずはコンパイルやリンクに必要なパッケージを一通りインストールしておく。
$ sudo apt-get -y install build-essential
次に、ライブラリにするソースコードを用意する。
今回は、次のように greet()
という関数を提供するライブラリを作ることにした。
$ cat << 'EOF' > greet.c #include <stdio.h> #include <stdlib.h> int greet(void) { printf("Hello, World!\n"); return EXIT_SUCCESS; } EOF
上記のライブラリのヘッダファイルを用意する。
$ cat << 'EOF' > greet.h extern int greet(void); EOF
そして、そのライブラリを使うアプリケーションのソースコードも用意しておく。
こちらもシンプルに、ライブラリの提供する greet()
関数を呼び出すだけのものにした。
$ cat << 'EOF' > main.c #include <stdlib.h> #include "greet.h" int main(int argc, char *argv[]) { greet(); return EXIT_SUCCESS; } EOF
静的ライブラリ
まずは静的ライブラリから作ってみる。 静的ライブラリというのは、それを使うアプリケーションに同梱する形で使われるライブラリのことをいう。
まずはライブラリのソースコードをコンパイルする。
$ gcc -c greet.c
コンパイルするとオブジェクトファイル (.o) ができる。
$ ls | grep \.o$ greet.o $ file greet.o greet.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
静的ライブラリを作るには ar コマンドを使ってオブジェクトファイルをまとめる。 ライブラリのファイル名は lib から始まるようにする。 今回の場合は libgreet になる。
$ ar rsv libgreet.a greet.o
ar: creating libgreet.a
a - greet.o
これで静的ライブラリ (.a) ができた。
$ ls | grep \.a$ libgreet.a $ file libgreet.a libgreet.a: current ar archive
中にまとめられているオブジェクトファイルは ar コマンドの t オプションで確認できる。
$ ar tv libgreet.a rw-r--r-- 0/0 1688 Jan 1 00:00 1970 greet.o
静的ライブラリを使って実行ファイルを作る
静的ライブラリの用意ができたので、それを使って実行ファイルを作ってみよう。
静的ライブラリを使うには、それを使うアプリケーションをコンパイルするときに -l オプションを指定する。 指定するのは lib を除いたライブラリの名前、つまり今回の場合は greet になる。 先ほど作った静的ライブラリはカレントディレクトリにあるので、その場所を -L オプションで指定するのもお忘れなく。
$ gcc -o main main.c -L. -lgreet
これで静的ライブラリを使った実行ファイルができた。
$ file main main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cf903474bb67cf497ea476b664d78fb2f703709b, for GNU/Linux 3.2.0, not stripped
実行すると、ちゃんと greet() 関数の結果が出力される。
$ ./main Hello, World!
静的ライブラリをリンカのパスが通った場所に置く
先ほどの例では、静的ライブラリがカレントディレクトリにあったので -L オプションでその場所を指定しなければいけなかった。 ただ、これを毎回やっていてはめんどくさい。 なので、作った静的ライブラリをリンカのパスの通った場所に置いてしまおう。
パスの場所は ldconfig コマンドに -v オプションを指定して実行すればわかる。 静的ライブラリを見つけて、それを使うのはリンカ (ld) なので、その設定をするのが ldconfig コマンドということになる。
$ sudo ldconfig -v | grep -v $'\t' /sbin/ldconfig.real: Can't stat /usr/local/lib/x86_64-linux-gnu: No such file or directory /sbin/ldconfig.real: Path `/usr/lib/x86_64-linux-gnu' given more than once /sbin/ldconfig.real: Path `/lib/x86_64-linux-gnu' given more than once /sbin/ldconfig.real: Path `/usr/lib/x86_64-linux-gnu' given more than once /sbin/ldconfig.real: Path `/usr/lib' given more than once /usr/lib/x86_64-linux-gnu/libfakeroot: /usr/local/lib: /lib/x86_64-linux-gnu: /sbin/ldconfig.real: /lib/x86_64-linux-gnu/ld-2.31.so is the dynamic linker, ignoring /lib:
少し話が脱線するけど、リンカがライブラリを見つける場所は /etc/ld.so.conf にもとづいて決められる。 見ると、どうやら /etc/ld.so.conf.d にある *.conf ファイルを読み込むようになっているようだ。
$ cat /etc/ld.so.conf include /etc/ld.so.conf.d/*.conf
例えば、この中で libc.conf というのも見ると /usr/local/lib という場所をリンカが見るように設定している。
$ cat /etc/ld.so.conf.d/libc.conf # libc default configuration /usr/local/lib
その内容にもとづいて、先ほど作った静的ライブラリを /usr/local/lib ディレクトリに移動してみよう。
$ sudo mv libgreet.a /usr/local/lib/
今度は gcc コマンドを実行するときに -L オプションをつけない。 パスが通っている場所に静的ライブラリを置いているので、リンカは libgreet.a を自動的に見つけることができる。
$ gcc -o main main.c -lgreet
もちろん、これでうまく実行ファイルを作ることができる。
$ ./main Hello, World!
完成した実行ファイルの依存ライブラリを確認するには ldd コマンドを使う。 静的ライブラリは実行ファイルに同梱されるため、ここには表示されないことを確認しておこう。
$ ldd main linux-vdso.so.1 (0x00007fff5a6b0000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f38e2463000) /lib64/ld-linux-x86-64.so.2 (0x00007f38e2663000)
共有ライブラリ
次は共有ライブラリを使ってみる。 共有ライブラリは静的ライブラリと違って、実行ファイルに通常であれば同梱されない。 静的ライブラリの内容は、実行ファイルを実行するときにライブラリの場所を解決した上で呼び出される。
共有ライブラリを作るときは -shared オプションをつけてコンパイルする。 -fPIC は、つけておくと色々と有利になるらしい。
$ gcc -shared -fPIC -o libgreet.so greet.c
これで共有ライブラリ (.so) ができる。
$ ls | grep \.so$ libgreet.so $ file libgreet.so libgreet.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=2ef213e24e03355a619ea26e40baa94c255c9aa9, not stripped
共有ライブラリを使って実行ファイルを作る
共有ライブラリを使って実行ファイルを作る方法は、静的ライブラリを使ったときと何ら変わらない。 -l オプションでライブラリ名を指定して -L オプションで静的ライブラリの場所を指定する。
$ gcc -o main main.c -L. -lgreet
これで実行ファイルができる。
$ file main main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb243320b69318e19e0b45975686aedba26c2b62, for GNU/Linux 3.2.0, not stripped
それでは、できあがった実行ファイルを実行してみよう。 が、これは何やらエラーになる。
$ ./main
./main: error while loading shared libraries: libgreet.so: cannot open shared object file: No such file or directory
これは実行ファイルが必要とする共有ライブラリをリンカが見つけることができないためだ。 ldd コマンドを使って実行ファイルが依存する共有ライブラリを調べると、たしかに libgreet.so を見つけることができていない。
$ ldd main linux-vdso.so.1 (0x00007ffd3efa5000) libgreet.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd038c7d000) /lib64/ld-linux-x86-64.so.2 (0x00007fd038e7d000)
これは例えば LD_LIBRARY_PATH という変数でライブラリの場所を指定することで解決できる。
$ LD_LIBRARY_PATH=. ./main Hello, World!
この変数を指定すると共有ライブラリの場所が解決できる。
$ LD_LIBRARY_PATH=. ldd main linux-vdso.so.1 (0x00007ffed05ef000) libgreet.so => ./libgreet.so (0x00007f14dc07d000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f14dbe84000) /lib64/ld-linux-x86-64.so.2 (0x00007f14dc089000)
共有ライブラリをリンカのパスが通った場所に置く
そうはいっても毎回 LD_LIBRARY_PATH を指定するのはありえない。 なので、リンカのパスが通った場所に共有ライブラリを置いてやろう。
$ sudo mv libgreet.so /usr/local/lib/
しかし、ただ置いただけでは認識されない。
$ ldd ./main linux-vdso.so.1 (0x00007fff31ffd000) libgreet.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2a03d2f000) /lib64/ld-linux-x86-64.so.2 (0x00007f2a03f2f000)
ldconfig コマンドに -p オプションを指定すると、ロード済みの共有ライブラリが得られる。 こちらにも表示されていない。
$ ldconfig -p | grep libgreet
リンカに共有ライブラリを認識させるには ldconfig をルート権限で実行する必要がある。
$ sudo ldconfig
これで libgreet.so が解決できるようになった。
$ ldconfig -p | grep libgreet libgreet.so (libc6,x86-64) => /usr/local/lib/libgreet.so $ ldd ./main linux-vdso.so.1 (0x00007ffd64ac5000) libgreet.so => /usr/local/lib/libgreet.so (0x00007f08499aa000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f08497b8000) /lib64/ld-linux-x86-64.so.2 (0x00007f08499be000)
これで晴れて先ほどの実行ファイルが実行できるようになる。
$ ./main Hello, World!
また、リンカに -L オプションでライブラリの場所を伝えなくても実行ファイルがコンパイルできるようになる。
$ gcc -o main main.c -lgreet
このように、実行時に共有ライブラリを解決するやり方を動的リンクという。
共有ライブラリを静的リンクして使う
共有ライブラリの内容を、あらかじめ実行ファイルに同梱してしまう方法もある。 これなら、実行時に共有ライブラリの場所を解決する必要がなくなる。 これは静的リンクという。
やり方は、実行ファイルをコンパイルするときに --static オプションをつける。
$ gcc --static -o main main.c -lgreet
すると実行ファイルは何の共有ライブラリにも依存しなくなる。
$ ldd main not a dynamic executable
この実行ファイルは、いずれの共有ライブラリが入っていなくても実行できる。
$ ./main Hello, World!
ただし、弱点もある。 実行ファイルに共有ライブラリを同梱する関係で、ファイルサイズが大きくなってしまう。 例えば、今回であれば 852kB になった。
$ du -h main 852K main
共有ライブラリを使う場合と比べてみよう。
$ gcc -o main main.c -lgreet
共有ライブラリを使った実行ファイルは 20kB で済んでいる。
$ du -h main 20K main
また、LGPL などのように、静的リンクしてしまうとアプリケーションの全てのソースコードがそのライセンスに適用されてしまうケースもあるので注意が必要だ。
まとめ
- アプリケーションの共通機能はライブラリにまとめることができる
- ライブラリには静的ライブラリと共有ライブラリがある
- 静的ライブラリはオブジェクトファイルをまとめて作る
- 静的ライブラリは実行ファイルに同梱される
- 共有ライブラリは実行時に解決する使い方 (動的リンク) とコンパイル時に解決する使い方 (静的リンク) がある
- 静的リンクするときはファイルサイズやライセンスの扱いに注意が必要となる
いじょう。