Skip to content

Latest commit

 

History

History
374 lines (264 loc) · 13 KB

001_make.markdown

File metadata and controls

374 lines (264 loc) · 13 KB

C言語のビルドについて

2015/02/19 Thu

目的

C言語の文法を理解することと、C言語のビルドを理解することは大分異なる。 今回は、C言語のビルドの仕組みを理解することに全力を注ぐ。

オマケとして、autotoolsの使い方を解説し、憧れのconfigureを理解する。

言葉の定義

まずは、ビルドとコンパイルの定義を明確にする。

ビルド

ソースコードのコンパイルやライブラリのリンクなどを行い、最終的な実行可能ファイルを作成すること。 また、そのような作業によって生成されたソフトウェアの版。

IT用語辞典 - e-Words [ビルド] より

コンパイル

プログラミング言語で書かれたコンピュータプログラム(ソースコード)を解析し、 コンピュータが直接実行可能な形式のプログラム(オブジェクトコード)に 変換すること。そのためのソフトウェアをコンパイラ(compiler)という。

IT用語辞典 - e-Words [コンパイル] より

両者の違い

ビルド >> コンパイル という認識で良い。 沢山のファイルをコンパイルして、リンクして、実行可能ファイルを作成する一連の流れがビルド

ツール類

コンパイラ

  1. gcc
  2. glibc
  3. llvm-gcc

細かいことは良く分かってないです。
方言というほどには、違わないようです。最適化の方法とかが違うとか、うんぬん・・・。

初心者レベルは、とにかくgcc使っておけば良いと理解しておきます。

ビルドツール

  1. make
  2. cmake
  3. scons
  4. 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言語のソースコードは、通常は複雑になりすぎるのを回避するために、役割でファイルに分けて管理します。

ソースコードが複数ファイルある場合、以下の手順で実行可能ファイルを作成します。

  1. それぞれのファイルを個別にコンパイルする。
  2. コンパイルして生成されたオブジェクトファイルをリンクして、一つにまとめる。

この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関数の宣言が書かれています。
これは、プロトタイプ宣言といいます。

上のコンパイルを順に追っていくと

  1. src1.cのコンパイル
    この時、main関数内でadd関数を呼んでいるため、add関数の宣言が事前に読み込まれていないとコンパイルエラーになります。
  2. src2.cのコンパイル
    このファイルは、特に依存関係は無いので普通にコンパイル出来ます。
  3. リンク リンクされる時に、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]

余談 -Lオプションが必要になった話

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です。

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って何やるの?

autoconfは、 configureを生成する。

環境調査とかするコードを書き越える。M4という言語です。
身構える必要なし、単純なスクリプト言語です。

automakeって何やるの?

automakeは、Makefile.inを生成する。

configureが利用する。Makefile.inを生成してくれます。
じつは、automakeを使わなくてもMakefile.inを自前で作れば、それでも大丈夫。

configureコマンドは何やるの?

configureコマンドを実行すると、Makefile.inを雛形にして、Makefileを出力する。

autotoolsを使うことの利点

しきたりに従って、configure.acMakefile.amを作成すれば
業界標準に近いconfigure make make installになります。

autotoolsの実技

ここで、一つ言っておきますが、手順を一つでも逆にしたりすると、途端に動かなくなります。 お気をつけ下さい・・・。

(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

憧れの3連コマンド

$ ./configure
$ make
$ make install

ビルドの雛形を作ったら、一度コミットした上で、必要なライブラリチェックなどを足していきましょう。
一つ言えることは、あこがれを手に入れるための代償として、相当な数のビルド関連ファイルが作成されます。
必要最低限のものを残して、削除してしまいましょう。

覚えて帰って欲しいこと。

autotoolsは、古きよきビルドツール作成キットです。
ルールに従うことで、環境に依存しないビルドが用意できます。