CUBE SUGAR CONTAINER

技術系のこと書きます。

C: 静的ライブラリと共有ライブラリについて

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 などのように、静的リンクしてしまうとアプリケーションの全てのソースコードがそのライセンスに適用されてしまうケースもあるので注意が必要だ。

まとめ

  • アプリケーションの共通機能はライブラリにまとめることができる
  • ライブラリには静的ライブラリと共有ライブラリがある
  • 静的ライブラリはオブジェクトファイルをまとめて作る
  • 静的ライブラリは実行ファイルに同梱される
  • 共有ライブラリは実行時に解決する使い方 (動的リンク) とコンパイル時に解決する使い方 (静的リンク) がある
  • 静的リンクするときはファイルサイズやライセンスの扱いに注意が必要となる

いじょう。