ソフトウェアインストール/プログラムコンパイルプロセス

21853 ワード

チェン一峰のブログから
ソースコードを実行するには、まずバイナリのマシンコードに変換する必要があります.これはコンパイラのタスクです.
たとえば、次のソースコード(ファイル名をtest.cと仮定します).

#include <stdio.h>

int main(void)
{
  fputs("Hello, world!
"
, stdout); return 0; }

まずコンパイラで処理してから実行します.

$ gcc test.c
$ ./a.out
Hello, world!

複雑なプロジェクトでは、コンパイルプロセスを3つのステップに分ける必要があります.

$ ./configure
$ make  
$ make install

これらの命令はいったい何をしているのか.多くの本や資料は、言葉がはっきりしていないので、これだけでコンパイルできると言って、さらなる解釈はありません.
コンパイラの作業手順、すなわち、上記の3つのコマンドのそれぞれのタスクについて説明します.Alex Smithの記事『Building C Projects』を主に参考にしました.なお,本稿では主にgccコンパイラ,すなわちCとC++について,必ずしも他の言語のコンパイルに適用されるとは限らない.

第1ステップ構成(configure)


コンパイラは、作業を開始する前に、標準ライブラリがどこにあるか、ソフトウェアのインストール場所がどこにあるか、どのコンポーネントをインストールする必要があるかなど、現在のシステム環境を知る必要があります.これは,異なるコンピュータのシステム環境が異なり,コンパイルパラメータを指定することでコンパイラが環境に柔軟に適応し,様々な環境で動作可能なマシンコードをコンパイルできるためである.このパラメータをコンパイルするステップを「構成」(configure)と呼びます.
これらの構成情報は1つの構成ファイルに保存され、configureというスクリプトファイルとして一般的に使用されます.通常はautoconfツールによって生成されます.コンパイラはこのスクリプトを実行することで、コンパイルパラメータを知ることができます.
configureスクリプトは、異なるシステムの違いをできるだけ考慮し、さまざまなコンパイルパラメータにデフォルト値を与えています.ユーザーのシステム環境が特別であるか、特定のニーズがある場合は、configureスクリプトにコンパイルパラメータを手動で提供する必要があります.

$ ./configure --prefix=/www --with-mysql

上のコードはphpソースのコンパイル構成で、ユーザーがインストールしたファイルをwwwディレクトリに保存し、コンパイル時にmysqlモジュールのサポートを追加することを指定します.

ステップ2標準ライブラリとヘッダファイルの場所を決定する


ソースコードは、標準ライブラリ関数(standard library)とヘッダファイル(header)を使用するに違いありません.システムの任意のディレクトリに格納できますが、コンパイラは実際にはプロファイルでしか認識できない場所を自動的に検出できません.
コンパイルの第2のステップは、プロファイルから標準ライブラリとヘッダファイルの場所を知ることです.一般的に、プロファイルには、いくつかの特定のディレクトリがリストされます.コンパイルを待つと、コンパイラはこれらのディレクトリに順番に移動し、ターゲットを探します.

ステップ3依存関係の決定


大規模なプロジェクトでは、ソースファイル間に依存関係があることが多く、コンパイラはコンパイルの前後順序を決定する必要があります.AファイルがBファイルに依存していると仮定すると、コンパイラは次の2点を保証する必要があります.
(1)Bファイルのコンパイルが完了してからのみ、Aファイルのコンパイルを開始する.
(2)Bファイルが変化すると,Aファイルは再コンパイルされる.
コンパイル順序はmakefileというファイルに保存され、どのファイルが先にコンパイルされ、どのファイルが後にコンパイルされるかがリストされます.makefileファイルはconfigureスクリプト実行によって生成されます.これは、コンパイル時にconfigureが最初に実行しなければならない理由です.
依存関係を決定すると同時に、コンパイラはコンパイル時にどのヘッダファイルを使用するかを決定します.

第4ステップヘッダファイルのプリコンパイル(precompilation)


異なるソースファイルは、同じヘッダファイル(例えばstdio.h)を参照することができます.コンパイルするときは、ヘッダファイルも一緒にコンパイルしなければなりません.時間を節約するために、コンパイラはソースコードをコンパイルする前に、ヘッダファイルをコンパイルします.これにより、ヘッダファイルを1回コンパイルするだけで、使用するたびに再コンパイルする必要がなくなります.
ただし、ヘッダファイルのすべての内容がプリコンパイルされるわけではありません.マクロを宣言するためのdefineコマンドは、プリコンパイルされません.

ステップ5前処理(Preprocessing)


プリコンパイルが完了すると、コンパイラはソースコードのbashのヘッダファイルとマクロを置き換え始めます.本明細書の冒頭のソースコードを例にとると、ヘッダファイルstdioが含まれる.h,置換後の様子は以下の通りである.

extern int fputs(const char *, FILE *);
extern FILE *stdout;

int main(void)
{
    fputs("Hello, world!
"
, stdout); return 0; }

読みやすいように、上のコードはヘッダファイルのソースコードに関する部分、すなわちfputsとFILEの声明のみを切り取り、stdioを省略する.hの他の部分(非常に長いため).また,上のコードのヘッダファイルはプリコンパイルされていないが,実際には,ソースコードを挿入したのはプリコンパイル後の結果である.コンパイラはこのステップでコメントも削除します.
このステップを「前処理」(Preprocessing)と呼びます.完了すると、本格的な処理が始まるからです.

ステップ6コンパイル(Compilation)


前処理後、コンパイラはマシンコードの生成を開始します.一部のコンパイラでは、ソースコードをアセンブリコード(assembly)に変換してから、アセンブリコードをマシンコードに変換する中間ステップも存在する.
次に、本明細書の先頭のソースコードから変換された符号化を示す.

    .file   "test.c"
    .section    .rodata
.LC0:
    .string "Hello, world!
"
.text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq stdout(%rip), %rax movq %rax, %rcx movl $14, %edx movl $1, %esi movl $.LC0, %edi call fwrite movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Debian 4.9.1-19) 4.9.1" .section .note.GNU-stack,"",@progbits

このトランスコードされたファイルをオブジェクトファイル(object file)と呼ぶ.

ステップ7リンク


オブジェクトファイルはまだ実行できません.さらに実行可能ファイルに移行する必要があります.前のステップのトランスコード結果をよく見ると、stdout関数とfwrite関数が参照されていることがわかります.すなわち,プログラムが正常に動作するには,上記のコードのほかにstdoutとfwriteの2つの関数のコードが必要であり,C言語の標準ライブラリから提供される.
コンパイラの次の作業は、外部関数のコード(通常は.libと.aという接尾辞のファイル)を実行可能ファイルに追加することです.これを接続(linking)と言います.このようなコピーにより、外部関数ライブラリを実行可能ファイルに追加する方法を静的接続(static linking)と呼び、後述する動的接続(dynamic linking)もあります.
makeコマンドの役割は、4ステップ目のヘッダファイルのプリコンパイルから、このステップを完了するまでです.

ステップ8インストール(Installation)


前の接続はメモリで行われました.つまり、コンパイラがメモリに実行可能なファイルを生成しました.次に、実行可能ファイルをユーザーが事前に指定したインストールディレクトリに保存する必要があります.
表面的には、実行可能なファイル(関連するデータファイル)をコピーすれば簡単です.しかし、実際には、ディレクトリの作成、ファイルの保存、権限の設定などの手順を完了する必要があります.この保存プロセス全体を「インストール」(Installation)と呼びます.

ステップ9オペレーティングシステム接続


ファイルのインストールを実行するには、このプログラムが使用可能であることをオペレーティングシステムに何らかの方法で通知する必要があります.たとえば、テキストリーダーをインストールし、txtファイルをダブルクリックすると自動的に実行されます.
これは、オペレーティングシステムにおいて、このプログラムのメタデータ、ファイル名、ファイル記述、関連接尾辞名などを登録する必要がある.Linuxシステムでは、これらの情報は通常/usr/share/applicationsディレクトリの下に保存する.desktopファイルにあります.また、Windowsオペレーティングシステムでは、スタートメニューでショートカットを作成する必要があります.
これらのことを「OS接続」と呼びます.make installコマンドは、「インストール」と「オペレーティングシステム接続」の2つのステップを完了するために使用されます.

ステップ10インストールパッケージの生成


ここまで書くと、ソースコードのコンパイルのプロセス全体がほぼ完了します.しかし、一部のユーザーだけが、我慢して、最初から最後までこの過程をしたいと思っています.実際、ソースコードだけをユーザーに渡すことができれば、彼らはあなたが友好的ではないやつだと認定します.ほとんどのユーザーが望んでいるのはバイナリの実行可能プログラムで、すぐに実行できます.これは開発者に,前回生成した実行可能ファイルを配布可能なインストールパッケージにすることを要求する.
したがって、コンパイラにはインストールパッケージを生成する機能も必要です.通常は、実行可能ファイル(関連するデータファイル)を、あるディレクトリ構造で圧縮ファイルパッケージとして保存し、ユーザーに渡す.

ステップ11動的接続(Dynamic linking)


通常、ここまで来ると、プログラムはすでに実行できます.実行中に発生したことは、コンパイラとは一切関係ありません.ただし、開発者は、コンパイルフェーズで実行可能なファイルが外部関数ライブラリに接続される方法を選択し、静的接続(コンパイル時接続)なのか、動的接続(ランタイム接続)なのかを選択できます.だから、最後に動的接続とは何かをお話しします.
前述したように、静的接続は外部関数ライブラリを実行可能ファイルにコピーすることです.このような利点は、適用範囲が広く、ユーザーマシンにライブラリファイルが欠けている心配がないことです.欠点は、インストールパッケージが大きく、複数のアプリケーション間でライブラリファイルを共有できないことです.動的接続のやり方は正反対で、外部関数ライブラリはインストールパッケージに入らず、実行時にのみ動的に参照されます.メリットは、インストールパッケージが小さく、複数のアプリケーションがライブラリファイルを共有できることです.欠点は、ユーザーが事前にライブラリファイルをインストールし、バージョンとインストール場所が要求に合致しなければ、正常に動作しないことです.
現実的には、ほとんどのソフトウェアはダイナミック接続を採用し、ライブラリファイルを共有しています.このような動的共有ライブラリファイルは、Linuxプラットフォームが接尾辞名である.soのファイル、Windowsプラットフォームは.dllファイル、Macプラットフォームは.dylibファイル.