2015/02/19 Thu
C言語の文法を理解することと、C言語のビルドを理解することは大分異なる。 今回は、C言語のビルドの仕組みを理解することに全力を注ぐ。
オマケとして、autotoolsの使い方を解説し、憧れのconfigure
を理解する。
まずは、ビルドとコンパイルの定義を明確にする。
ソースコードのコンパイルやライブラリのリンクなどを行い、最終的な実行可能ファイルを作成すること。 また、そのような作業によって生成されたソフトウェアの版。
IT用語辞典 - e-Words [ビルド] より
プログラミング言語で書かれたコンピュータプログラム(ソースコード)を解析し、 コンピュータが直接実行可能な形式のプログラム(オブジェクトコード)に 変換すること。そのためのソフトウェアをコンパイラ(compiler)という。
IT用語辞典 - e-Words [コンパイル] より
ビルド >> コンパイル という認識で良い。
沢山のファイルをコンパイル
して、リンクして、実行可能ファイルを作成する一連の流れがビルド
- gcc
- glibc
- llvm-gcc
細かいことは良く分かってないです。
方言というほどには、違わないようです。最適化の方法とかが違うとか、うんぬん・・・。
初心者レベルは、とにかくgcc
使っておけば良いと理解しておきます。
- make
- cmake
- scons
- gyp
っていうか、これについても2番以降はよく分からない。
初心者レベルは、とにかくmake
でビルドすると理解しておきます。
後述のautotoolsも最終的には、make
を使います。
例)
$ gcc -o hello hello.c
#include <stdio.h>
int main(){
printf("Hello World\n");
exit;
}
gcc
コマンドを使って、C言語のソースコードをオブジェクトファイルに変換します。
main関数が含まれ、かつリンクが解決されている場合は、その時点で実行可能ファイルです。
この場合は、ビルド ≒ コンパイル
実際にhello
コマンドを作成してみます。
- o
その名前で実行ファイルを作る。指定しない場合は、a.out
という名前でコンパイルされる。 - c
リンクを行わない。preprocess, complile, assembleのみ行う。
大きく2種類ある。
実行可能ファイルそのものには、含まれていないコードを実行時に読み込むことを動的リンクといいます。
#include <stdio.h>
このC言語ファイルのお約束の一文は、共有ライブラリの読み込みを表しています。 実行時に共有ライブラリを読み込んで、その内容を利用するという意味です。
stdio.h
はStandard(標準)のIO(入出力)の共有ライブラリを読み込みます。
特定の実行ファイルについて、どの共有ライブラリがリンクされているのかを調べるには、OS毎に下記のコマンドで行います。
- Linux系OS
ldd
- Mac OSX
otool -L
- Windows
分かんない・・・
先ほど作成したhello
コマンドの共有ライブラリを調べます。
otool -L hello
Macだとdylib
, linuxだと.so
, Windowsだと dll
が動的リンクされてます。
C言語のソースコードは、通常は複雑になりすぎるのを回避するために、役割でファイルに分けて管理します。
ソースコードが複数ファイルある場合、以下の手順で実行可能ファイルを作成します。
- それぞれのファイルを個別にコンパイルする。
- コンパイルして生成されたオブジェクトファイルをリンクして、一つにまとめる。
この2の手順は、最終的に生成される実行可能ファイルの中に、全てのファイルの内容が含まれます。
このように、実行可能ファイルに含む形でリンクを行うことを静的リンクと呼びます。
- src1.c
#include <stdio.h>
int add(int x, int y);
int main (){
int z = add(7, 5);
printf("result is %d\n", z);
return 1;
}
- src2.c
#include <stdio.h>
int add(int x, int y){
return x + y ;
}
コンパイルとリンク
$ gcc -c src1.c
$ gcc -c src2.c
$ gcc -o add src1.o src2.o
src1.cの3行目にadd
関数の宣言が書かれています。
これは、プロトタイプ宣言といいます。
上のコンパイルを順に追っていくと
- src1.cのコンパイル
この時、main関数内でadd関数を呼んでいるため、add関数の宣言が事前に読み込まれていないとコンパイルエラーになります。 - src2.cのコンパイル
このファイルは、特に依存関係は無いので普通にコンパイル出来ます。 - リンク リンクされる時に、src1.cのadd関数の宣言と、src2.cのadd関数の実装が紐付けされます。
Cのプロジェクトでは、プロトタイプ宣言だけを一つのファイルにまとめて、共通ファイルとして使います。
この共通ファイルのことをヘッダーファイルと呼びます。この共通ファイルは、{name}.h
という風に拡張子hを付けた名前で作成します。
gcc
は以下のディレクトリの順番で、 #include
で指定した共有ライブラリのヘッダーファイルを検索します。
/usr/local/include
libdir/gcc/target/version/include
/usr/target/include
/usr/include
-I
オプションは、ヘッダーファイルの検索先ディレクトリを探索対象ディレクトリの先頭に追加できます。
include
されたヘッダーファイルの実体は、共有ライブラリモジュールに入ってます。
その共有ライブラリモジュールの探索先は/etc/ld.so.conf
で定義されています。
Linuxの場合、ldconfig -p
で認識されている共有ライブラリモジュールが確認できます。
-L
オプションで、探索先の先頭にディレクトリを追加できます。
gccで直接コンパイルする場合は、上記のオプションが使えるのですが、OSSプロジェクトとかだと makeコマンドでビルドを行うため、gccのオプションを直接はいじれません。
その場合は環境変数を利用します。
-
LD_FLAGS
configure
コマンドに渡すことで、gcc
実行時に任意のオプションを渡せます。./configure LDFLAGS=-L/fuga/hoge
※configureの詳細については、後述。 -
LD_LIBRARY_PATH
-L
に渡すパスを追加すると、コンパイル時に共有ライブラリを探しにいってくれます。
mac osxの場合は、DYLD_LIBRARY_PATH
です。
man dyld
してみると、リンク関連の情報が色々みれます。[Mac OSX]
readline
というライブラリを使うソースコードを書いて、gcc
でコンパイルしたが、どうしても上手く動かない。
原因は、Macのreadline
のライブラリがeditline
という別物のシンボリックリンクだったためです。
別途brew
でインストールしたreadline
があったので、-L
オプションでそのreadeline
ライブラリを指定することで、
正常動作する実行可能ファイルの生成に成功しました。
ソースコードが増えてきて、複雑さが増してくると、実行可能ファイルの生成も非常に大変になります。
それぞれのCのソースをオブジェクトファイルにコンパイルして、リンクして・・・。非常にしんどいです。
しんどいけど機械的な作業は自動化するのが一番・・・というわけでmakeが誕生しました。
C言語の登場が1972年、makeが登場したのが1978年 makeは僕と同級生。
さっきの例もしんどいのでmakeにまかせてしまいましょう。
makeは、デフォルトでコマンドが実行されたディレクトリ配下のMakefile
というファイルに書かれている内容を読み込んで実行します。
all: clean src1.o src2.o
gcc -o add src1.o src2.o
src1.o:
gcc -c src1.c
src2.o:
gcc -c src2.c
clean:
rm -f *.o
1つだけ覚えておくべきmake
の鉄則があります。それは「字下げはタブ」です。
これを守らないと、make
は実行できません。
makeは、拡張子がoのオブジェクトファイルの更新を感知して、再コンパイルを行います。 なので、実行可能ファイルを更新する場合はオブジェクトファイルを削除する必要があります。
Makefile内では変数も扱えます。${CC}
とかやると先頭でセットしたCC
という値を参照できます。
makeはとてもシンプルです。Makefileに書かれている指示を忠実に再現します。
ただし、makeは動的要素に弱いです。たとえば、環境の違いによって、共有ライブラリを見つけられない場合や、依存するライブラリのバージョンをチェックしたい場合
makeにそれらのチェックコマンドを書くのは大変です。(シェルっぽい記述ができるのでやろうと思えばやれると思うけど・・・)
そこで登場するのがAutotoolsです。
autoconf
, automake
, libtools
の3つから成り立っている。
すでに殆どの皆さんは、これの恩恵にあずかっているはず。
$ ./configure
$ make
$ make install
これは、autotools
を使うことで簡単?に作成することが出来ます。
Autotoolsの情報は、色んな場所に四散していて、本来単純なツールなはずが、わかりにくくなっています。
Wikipediaの記述が結局は、一番分かりやすかったす。
- autoconf
configure.in または configure.ac というファイルを入力として、 configureを生成するツール。 - automake
Makefile.amを入力として、Makefile.inを生成するツール。 - libtool
環境依存をなるべく避けたライブラリの作成とかに使うツールらしい。 詳細は調べてない。まあ生きるのに支障はない。
autoconfは、 configureを生成する。
環境調査とかするコードを書き越える。M4という言語です。
身構える必要なし、単純なスクリプト言語です。
automakeは、Makefile.inを生成する。
configureが利用する。Makefile.inを生成してくれます。
じつは、automakeを使わなくてもMakefile.inを自前で作れば、それでも大丈夫。
configureコマンドを実行すると、Makefile.inを雛形にして、Makefileを出力する。
しきたりに従って、configure.ac
とMakefile.am
を作成すれば
業界標準に近いconfigure
make
make install
になります。
ここで、一つ言っておきますが、手順を一つでも逆にしたりすると、途端に動かなくなります。 お気をつけ下さい・・・。
(1) configure.ac
の雛形を作成
autoscan
(2) configure.scan
を改名
mv configure.scan configure.ac
(3) Makefile.am
を作成
bin_PROGRAMS=adder
adder_SOURCES=src1.c src2.c
(4) automake
を利用することをconfigure.ac
へ追記
AM_INIT_AUTOMAKE
AC_CONFIG_FILES([Makefile])
(5) automake
に必要なm4ライブラリを用意
aclocal
(6) automake
に必要なツールをインストール
automake -a -c
(7) automake
で必須とされるファイルを準備
touch NEWS README AUTHORS ChangeLog
(8) Makefile.in
を作成
automake
(9) configure
を作成
autoconf
$ ./configure
$ make
$ make install
ビルドの雛形を作ったら、一度コミットした上で、必要なライブラリチェックなどを足していきましょう。
一つ言えることは、あこがれを手に入れるための代償として、相当な数のビルド関連ファイルが作成されます。
必要最低限のものを残して、削除してしまいましょう。
autotools
は、古きよきビルドツール作成キットです。
ルールに従うことで、環境に依存しないビルドが用意できます。