GNUコンパイラgccとg++

序文

この序文は、GNUプロジェクトの創設者でありGCCの主要著者であるリチャード・M・ストールマンによって親切に貢献されました。

この本は、GNU Compiler CollectionであるGCCを始めるためのガイドです。 GCCをプログラミングツールとして使用する方法を説明します。 GCCはプログラミングツールですが、これは真実ですが、それはさらに何かです。これは、コンピュータユーザーのための20年間のキャンペーンの一環です。私たちは皆良いソフトウェアを求めていますが、ソフトウェアが「良い」とはどういう意味ですか?便利な機能と信頼性は、技術的に優れていることを意味しますが、それだけでは不十分です。良いソフトウェアは倫理的にも良いものでなければならず、ユーザーの自由を尊重しなければなりません。

ソフトウェアのユーザーは、適切に実行する権利、ソースコードを勉強して適切に変更する権利、他の人にそのコピーを再配布する権利、および公開する権利が必要ですあなたがコミュニティの構築に貢献できるように変更されたバージョン。プログラムがあなたの自由をこのように尊重するとき、私たちはそれをフリーソフトウェアと呼んでいます。 GCCの前には、C、Fortran、Adaなどのコンパイラがありましたが、それらはフリーソフトウェアではありませんでした。あなたは自由でそれらを使うことができませんでした。私はGCCを書いて、私たちの自由をあきらめずにコンパイラを使うことができました。

コンパイラだけでは不十分です。コンピュータシステムを使用するには、オペレーティングシステム全体が必要です。 1983年には、最新のコンピュータ用のオペレーティングシステムはすべて無料であった。この問題を解決するために、私は1984年にフリーソフトウェアとなるUnixのようなシステムであるGNUオペレーティングシステムの開発に着手しました。 GCCの開発は、GNU開発の一環でした。

90年代初期までに、ほぼ完成したGNUオペレーティングシステムは、1992年にフリーソフトウェアとなったカーネルであるLinuxを追加することで完成しました。GNU / Linuxオペレーティングシステムを組み合わせることで、自由。しかし、自由は決して自動的に安全ではなく、我々はそれを守るために働く必要があります。フリーソフトウェア運動は、あなたのサポートを必要とします。

1はじめに

この本の目的は、GNU CおよびC ++コンパイラgccおよびg ++の使用方法を説明することです。 この本を読んだら、プログラムのコンパイル方法と、最適化とデバッグのための基本コンパイラオプションの使い方を理解する必要があります。 この資料は、他の多くの場所で見つけることができるので、C言語やC ++言語自体を教えようとはしません(see section追加情報)。

他のシステムに精通していてGNUコンパイラには初めての経験豊富なプログラマは、「Cプログラムのコンパイル」、「プリプロセッサの使用」、および「C ++プログラムのコンパイル」の章の前半をスキップできます。 残りの章と章では、他のコンパイラの使い方をすでに知っている人たちのために、GCCの機能をよく理解しておくべきです。

1.1 GCCの歴史

GNU CコンパイラGCC)の元の著者は、GNUプロジェクトの創始者であるRichard Stallmanです。

GNUプロジェクトは1984年に開始され、コンピュータユーザーとプログラマの自由と協力を促進するために、フリーソフトウェアとして完全なUnixライクなオペレーティングシステムを作成しました。すべてのUnixライクなオペレーティングシステムにはCコンパイラが必要です。その時点でフリーコンパイラが存在しないため、GNUプロジェクトはゼロから開発する必要がありました。この作品は、個人および企業からのGNUプロジェクトの活動を支援する非営利団体であるフリーソフトウェア財団への寄付によって資金提供されました。

GCCの最初のリリースは1987年に行われました。これは大きな成功を収めました。これは、移植性の高いANSI Cコンパイラフリーソフトウェアとして初めてリリースされたことです。その時以来、GCCフリーソフトウェアの開発において最も重要なツールの1つになっています。

コンパイラの主要な改訂版には、1992年の2.0シリーズが付属しており、C ++をコンパイルする機能が追加されました。 1997年に最適化とC ++サポートを改善するために、コンパイラの実験ブランチ(EGCS)が作成されました。この作業の後、EGCSはGCC開発の新しいメインラインとして採用され、これらの機能は2001年にGCCの3.0リリースで広く利用できるようになりました。

時間が経つと、GCCFortran、ADA、JavaObjective-Cなどの多くの言語をサポートするように拡張されました。頭字語のGCCは現在、「GNU Compiler Collection」を参照するために使用されています。その発展は、産業、研究、学術界のGCCユーザーコミュニティの代表者で構成されるGCC運営委員会によって導かれます。

1.2 GCCの主な特徴

このセクションでは、GCCの最も重要な機能について説明します。

まず第一に、GCCは移植可能なコンパイラです。現在使用可能なほとんどのプラットフォームで動作し、多くのタイプのプロセッサの出力を生成できます。パーソナルコンピュータで使用されるプロセッサに加えて、マイクロコントローラ、DSP、および64ビットCPUもサポートしています。

GCCは、ネイティブコンパイラだけでなく、GCC自身が使用するものとは異なるシステム用の実行可能ファイルを生成する任意のプログラムをクロスコンパイルすることもできます。これにより、コンパイラを実行できない組み込みシステム向けにソフトウェアをコンパイルすることができます。 GCCは移植性に重点を置いてC言語で書かれており、コンパイルすることができるので、新しいシステムに簡単に適応できます。

GCCには、さまざまな言語を解析するための複数の言語フロントエンドがあります。各言語のプログラムは、任意のアーキテクチャコンパイルまたはクロスコンパイルできます。たとえば、ADAプログラムをマイクロコントローラ用にコンパイルしたり、スーパーコンピュータ用にCプログラムをコンパイルしたりすることができます。

GCCにはモジュラー設計が採用されているため、新しい言語やアーキテクチャを追加することができます。 GCCに新しい言語フロントエンドを追加することで、必要なランタイム機能(ライブラリなど)が利用可能であれば、その言語を任意のアーキテクチャで使用することができます。同様に、新しいアーキテクチャのサポートを追加すると、すべての言語で使用できるようになります。

最後に、最も重要なのは、GCCGNU General Public License(GNU GPL)に基づいて配布されているフリーソフトウェアです。(1)これは、すべてのGNUソフトウェアと同様に、GCCの使用と変更を自由に行えることを意味します。新しいタイプのCPU、新しい言語、または新しい機能のサポートが必要な場合は、自分で追加したり、GCCを強化するために誰かを雇うことができます。あなたの仕事にとって重要なバグを修正するために誰かを雇うことができます。

さらに、あなたはGCCに対してあなたが行った拡張機能を自由に共有することができます。この自由の結果、他の人が開発したGCC拡張機能を利用することもできます。 GCCが提供する多くの機能は、この協力の自由がどのようにあなたの利益になるのか、そしてGCCを使用するすべての人々が今日どのように役立つのかを示しています。

1.3 CおよびC ++でのプログラミング

CおよびC ++は、コンピュータのメモリに直接アクセスできる言語です。 歴史的に、低レベルのシステムソフトウェアや、リソースの使用率の高さや制御が重要なアプリケーションの作成に使用されてきました。 ただし、他のデータ構造の破壊を避けるために、メモリが正しくアクセスされるように注意する必要があります。 このマニュアルでは、コンパイル時の潜在的なエラーを検出するためのテクニックについて説明しますが、CやC ++などの言語を使用するリスクを排除することはできません。

GNU Common Lispgcl)、GNU Smalltalk(gst)、GNU Scheme拡張言語(guile)、JavaGNUコンパイラgcj)などの他の高級言語も、CおよびC ++に加えて提供しています。 これらの言語では、メモリアクセスエラーの可能性を排除して、メモリに直接アクセスすることはできません。 これらは、多くのアプリケーションでCおよびC ++のより安全な代替手段です。

1.4このマニュアルで使用されている表記規則

このマニュアルには、キーボードで入力できる多くの例が含まれています。 端末に入力されたコマンドは次のように表示されます。

$ command

それに続いて出力されます。例えば:

$ echo "hello world"
hello world

行の最初の文字はターミナルプロンプトで、入力しないでください。 このマニュアルではドル記号「$」が標準プロンプトとして使用されていますが、システムによっては異なる文字を使用するものもあります。

例のコマンドが長すぎて1行に収まらない場合、次のように次の行に折り返されてインデントされます。

$ echo "an example of a line which is too long to fit 
    in this manual"

キーボードで入力するときは、コマンド全体を1行に入力する必要があります。

このマニュアルで使用されているソースファイルの例は、出版社のWebサイトからダウンロードすることができます(2)。または、標準のGNUエディタemacsなどのテキストエディタを使用して手動で入力します。 コンパイルコマンドの例では、GNU CおよびC ++コンパイラの名前としてgccおよびg ++を使用し、他のコンパイラを参照するためにccを使用します。 サンプルプログラムはGCCのどのバージョンでも動作するはずです。 最近のバージョンのGCCでのみ利用可能なコマンドラインオプションは、本文に記載されています。

この例では、GNUオペレーティングシステムの使用を前提としています。他のシステムの出力には若干の違いがあるかもしれません。 簡潔にするため、いくつかの重要でないシステム依存の出力メッセージ(非常に長いシステムパスなど)が例で編集されています。 環境変数を設定するコマンドは、標準のGNUシェル(bash)の構文を使用し、Bourneシェルのどのバージョンでも動作します。

2 Cプログラムのコンパイル

この章では、gccを使用してCプログラムをコンパイルする方法について説明します。 プログラムは、単一のソースファイルまたは複数のソースファイルからコンパイルすることができ、システムライブラリとヘッダファイルを使用することができます。

コンパイルとは、プログラムをテキストソースコードからCやC ++などのプログラミング言語で機械コードに変換するプロセスで、コンピュータの中央処理装置(CPU)を制御するために使用される1と0のシーケンスです。 このマシンコードは、バイナリファイルと呼ばれる実行可能ファイルと呼ばれるファイルに格納されます。

2.1単純なCプログラムのコンパイル

C言語の典型的なサンプルプログラムはHello Worldです。 ここに、私たちのバージョンのプログラムのソースコードがあります:

#include <stdio.h>

int
main (void)
{
  printf ("Hello, world!\n");
  return 0;
}

ソースコードは「hello.c」というファイルに格納されているものとします。 ‘hello.c'ファイルをgccコンパイルするには、次のコマンドを使用します。

$ gcc -Wall hello.c -o hello

‘hello.c'のソースコードをマシンコードにコンパイルし、実行ファイル 'hello'に格納します。マシンコードの出力ファイルは、-oオプションを使用して指定します。このオプションは通常、コマンドラインの最後の引数として与えられます。これを省略すると、出力は 'a.out'というデフォルトファイルに書き込まれます。

実行可能ファイルと同じ名前のファイルがカレントディレクトリにすでに存在する場合は上書きされます。

オプション-Wallは、最も一般的に使用されるすべてのコンパイラ警告をオンにします—このオプションを常に使用することをお勧めします!後の章で説明する他の多くの警告オプションがありますが、-Wallが最も重要です。 GCCは、有効にされていない限り警告を出力しません。コンパイラの警告は、CおよびC ++でプログラミングするときに問題を検出する上で不可欠な助けとなります。

この場合、プログラムが完全に有効であるため、コンパイラは-Wallオプションで警告を生成しません。警告を生成しないソースコードは、きちんとコンパイルされていると言われています。

プログラムを実行するには、次のように実行可能ファイルのパス名を入力します。

$ ./hello
Hello, world!

これは、実行可能ファイルをメモリにロードし、CPUにその中に含まれる命令の実行を開始させる。 パス./は現在のディレクトリを参照するため、./helloは現在のディレクトリにある実行可能ファイル ‘hello'をロードして実行します。

2.2簡単なプログラムでのエラーの発見

上で述べたように、コンパイラの警告は、CおよびC ++でプログラミングする際の基本的な助けとなります。 これを実証するために、以下のプログラムには微妙なエラーがあります。整数値に浮動小数点フォーマット ‘%f'を指定することによって、関数printfを間違って使います:

#include <stdio.h>

int
main (void)
{
  printf ("Two plus two is %f\n", 4);
  return 0;
}

このエラーは一見したところ明らかではありませんが、警告オプション-Wallが有効になっているとコンパイラが検出できます。 上のプログラム ‘bad.c'を警告オプション-Wallとともにコンパイルすると、次のメッセージが生成されます。

$ gcc -Wall bad.c -o bad
bad.c: In function `main':
bad.c:6: warning: double format, different 
  type arg (arg 2)

これは、フォーマット文字列が6行目の ‘bad.c'ファイルで不正に使用されたことを示しています.GCCによって生成されるメッセージは、常にfile:line-number:メッセージの形式をとります。 コンパイラは、正常なコンパイルを妨げるエラーメッセージと、考えられる問題を示す警告メッセージ(コンパイルからプログラムを停止しない)を区別します。

この場合、整数引数の正しい書式指定子は ‘%d'でなければなりません。 printfのフォーマット指定子は、Cの一般的な書籍(GNU C Library Reference Manualなどを参照してください)にあります。

警告オプション-Wallを指定しないと、プログラムは正常にコンパイルされたように見えますが、不正な結果が生成されます。

$ gcc bad.c -o bad
$ ./bad
Two plus two is 2.585495    (incorrect output)

不正な書式指定子は、関数printfが浮動小数点数ではなく整数を渡すため、出力が破損する原因となります。 整数と浮動小数点数は異なるフォーマットでメモリに格納され、一般に異なるバイト数を占め、偽の結果につながります。 上記の実際の出力は、特定のプラットフォームおよび環境によって異なる場合があります。

明らかに、コンパイラの警告をチェックせずにプログラムを開発することは非常に危険です。 正しく使用されていない関数があると、プログラムがクラッシュしたり、誤った結果をもたらす可能性があります。 コンパイラ警告オプションをオンにすると、-WallはCプログラミングで発生する最も一般的なエラーの多くを捕捉します。

2.3複数のソースファイルのコンパイル

プログラムは複数のファイルに分割できます。 これにより、特に大きなプログラムの場合には、編集や理解が容易になります。また、個々のパーツを個別にコンパイルすることもできます。

次の例では、プログラムHello Worldを ‘main.c'、 'hello_fn.c'、およびヘッダーファイル 'hello.h'の3つのファイルに分割します。 ここにメインプログラム 'main.c'があります:

#include "hello.h"

int
main (void)
{
  hello ("world");
  return 0;
}

以前のプログラム ‘hello.c'のprintfシステム関数への元の呼び出しは、新しい外部関数helloの呼び出しに置き換えられました。これは別のファイル 'hello_fn.c'で定義します。

メインプログラムには、hello関数の宣言を含むヘッダファイル ‘hello.h'も含まれています。 この宣言は、関数呼び出しと関数定義の間で引数と戻り値の型が正しく一致するようにするために使用されます。 'main.c'はprintfを直接呼び出さないので、printf関数を宣言するために、システムヘッダファイル 'stdio.h'を 'main.c'にインクルードする必要はありません。

‘hello.h'の宣言は、関数helloのプロトタイプを指定する1行です:

void hello (const char * name);

関数helloの定義自体は、ファイル ‘hello_fn.c'に含まれています。

#include <stdio.h>
#include "hello.h"

void 
hello (const char * name)
{
  printf ("Hello, %s!\n", name);
}

この関数は、 “Hello、name!"というメッセージを表示します。 その引数をnameの値として使用します。

ちなみに、インクルードステートメント#include “FILE.h"と#include <FILE.h>の2つの形式の違いは、前者が現在のディレクトリで ‘FILE.h'を検索してから、システムヘッダファイルのディレクトリ 。 includeステートメント#include <FILE.h>はシステムヘッダファイルを検索しますが、デフォルトではカレントディレクトリを検索しません。

これらのソースファイルをgccコンパイルするには、次のコマンドを使用します。

$ gcc -Wall main.c hello_fn.c -o newhello

この場合、-oオプションを使用して、実行可能ファイル ‘newhello'に異なる出力ファイルを指定します。 コマンドラインのファイルのリストには、ヘッダーファイル 'hello.h'が指定されていないことに注意してください。 ソースファイルの#include “hello.h"は、適切なポイントに自動的にインクルードするようにコンパイラーに指示します。

プログラムを実行するには、実行可能ファイルのパス名を入力します。

$ ./newhello
Hello, world!

プログラムのすべての部分が1つの実行可能ファイルに結合されています。これは、以前使用された単一のソースファイルから作成された実行可能ファイルと同じ結果を生成します。

2.4独立してファイルをコンパイルする

プログラムが1つのファイルに格納されている場合、個々の関数を変更するには、プログラム全体を再コンパイルして新しい実行可能ファイルを生成する必要があります。大きなソースファイルの再コンパイルには非常に時間がかかることがあります。

プログラムが独立したソースファイルに格納されている場合、変更されたファイルのみがソースコードが変更された後に再コンパイルする必要があります。このアプローチでは、ソースファイルは別々にコンパイルされ、リンクされて2段階のプロセスになります。最初の段階では、実行可能ファイルを作成せずにファイルがコンパイルされます。結果はオブジェクトファイルと呼ばれ、GCCを使用するときは拡張子 ‘.o'を持ちます。

第2段階では、オブジェクトファイルは、リンカーと呼ばれる別のプログラムによって統合されます。リンカはすべてのオブジェクトファイルを結合して単一の実行可能ファイルを作成します。

オブジェクトファイルには、他のファイルの関数(または変数)のメモリアドレスへの参照が定義されていないマシンコードが含まれています。これにより、ソースファイル同士を直接参照せずにコンパイルすることができます。リンカは、実行可能ファイルを生成するときに、これらの欠落したアドレスを埋めます。

2.4.1ソースファイルからのオブジェクトファイルの作成

コマンドラインオプション-cを使用して、ソースファイルをオブジェクトファイルにコンパイルします。たとえば、次のコマンドは、ソースファイル ‘main.c'をオブジェクトファイルにコンパイルします。

$ gcc -Wall -c main.c

main関数のマシンコードを含むオブジェクトファイル ‘main.o'が生成されます。これには外部関数helloへの参照が含まれていますが、対応するメモリアドレスはこの段階でオブジェクトファイルに未定義のままです(後でリンクすると埋め込まれます)。

ソースファイル ‘hello_fn.c'のhello関数をコンパイルするコマンドは次のとおりです。

$ gcc -Wall -c hello_fn.c

これにより、オブジェクトファイル ‘hello_fn.o'が生成されます。

この場合、オプション-oを使用して出力ファイルの名前を指定する必要はありません。 -cを指定してコンパイルすると、コンパイラは自動的に元の拡張子ではなく ‘.o'を使用してソースファイルと同じ名前のオブジェクトファイルを作成します。

‘main.c'と 'hello_fn.c'の#include文で自動的にインクルードされるため、ヘッダファイル 'hello.h'をコマンドラインに置く必要はありません。

2.4.2オブジェクトファイルからの実行ファイルの作成

実行可能ファイルを作成する最後のステップは、gccを使用してオブジェクトファイルをリンクし、外部関数の欠落アドレスを埋め込むことです。オブジェクトファイルをリンクするには、単にコマンドラインに列挙します:

$ gcc main.o hello_fn.o -o hello

個々のソースファイルが既にオブジェクトコードに正常にコンパイルされているため、-Wall警告オプションを使用する必要はほとんどありません。ソースファイルがコンパイルされると、リンクは成功するか失敗するかの明白なプロセスです(解決できない参照がある場合にのみ失敗します)。

リンク手順を実行するために、gccは別のプログラムであるリンカldを使用します。 GNUシステムでは、GNUリンカーGNU ldが使用されます。他のシステムでは、GNUリンカをGCCと一緒に使用するか、独自のリンカを使用することがあります。リンカそのものについては後述します(see section 11コンパイラの仕組み)。リンカーを実行することによって、gccはオブジェクトファイルから実行可能ファイルを作成します。

結果の実行可能ファイルを今すぐ実行することができます:

$ ./hello
Hello, world!

これは、前のセクションで単一のソースファイルを使用しているプログラムのバージョンと同じ出力を生成します。

2.5再コンパイルと再リンク

ソースファイルを独自にコンパイルする方法を示すために、メインプログラム ‘main.c'を編集し、それを修正して、世界中ではなくすべての人に挨拶を出力します:

#include "hello.h"

int
main (void)
{
  hello ("everyone");  /* changed from "world" */
  return 0;
}

更新されたファイル ‘main.c'は次のコマンドで再コンパイルできるようになりました:

$ gcc -Wall -c main.c

これにより、新しいオブジェクトファイル ‘main.o'が作成されます。 'hello_fn.c'の新しいオブジェクトファイルを作成する必要はありません。ヘッダーファイルなどの依存するファイルや関連ファイルは変更されていないためです。

新しいオブジェクトファイルをhello関数で再リンクして新しい実行可能ファイルを作成することができます:

$ gcc main.o hello_fn.o -o hello

結果として得られる実行可能ファイル ‘hello'は、新しいmain関数を使用して次の出力を生成するようになりました。

$ ./hello
Hello, everyone!

ファイル ‘main.c'のみが再コンパイルされ、hello関数用の既存のオブジェクトファイルと再リンクされることに注意してください。 代わりに 'hello_fn.c'というファイルが変更されている場合は、 'hello_fn.c'を再コンパイルして新しいオブジェクトファイル 'hello_fn.o'を作成し、既存のファイル 'main.o'に再リンクすることができました。

多くのソースファイルを持つ大規模なプロジェクトでは、変更されたものだけを再コンパイルすることで大幅な節約が可能です。 プロジェクト内の変更されたファイルのみを再コンパイルするプロセスは、標準のUnixプログラムメイクで自動化することができます。

2.6 単純なメークファイル

makeに慣れていない人には、このセクションではその使い方を簡単に説明します。 makeはそれ自身のプログラムであり、すべてのUnixシステム上にあります。 GNU版のmakeの詳細については、Richard M. StallmanとRoland McGrathのGNU Makeマニュアルを参照する必要があります(see section追加情報)。

Makeは、メークファイルからプロジェクトの説明を読み込みます(デフォルトでは、現在のディレクトリの ‘Makefile'と呼ばれます)。 makefileは、ターゲット(実行可能ファイルなど)とそれらの依存関係(オブジェクトファイルやソースファイルなど)に関するコンパイルルールのセットを次の形式で指定します。

target: dependencies
        command

各ターゲットに対して、makeは対応する依存ファイルの変更時刻をチェックして、対応するコマンドを使用してターゲットを再構築する必要があるかどうかを判断します。 makefileコマンドラインはスペースではなく、単一のTAB文字でインデントする必要があることに注意してください。

GNU Makeには、Makefileの構築を簡単にするために、暗黙ルールと呼ばれる多くのデフォルトルールが含まれています。たとえば、 ‘.o'ファイルはコンパイルによって ’.c'ファイルから取得でき、 ‘.o'ファイル同士をリンクすることで実行可能であることが指定されています。暗黙的な規則は、CC(Cコンパイラ)やCFLAGS(Cプログラムのコンパイルオプション)などのmake変数の観点から定義され、makefileのVARIABLE = VALUE行を使用して設定できます。 C ++の場合、同等の変数はCXXとCXXFLAGSであり、make変数CPPFLAGSはプリプロセッサのオプションを設定します。暗黙のルールとユーザー定義のルールは、GNU Makeが必要に応じて自動的に連鎖します。

上記のプロジェクトのための単純な「Makefile」は、次のように書くことができます:

CC=gcc 
CFLAGS=-Wall
main: main.o hello_fn.o

clean:
    rm -f main main.o hello_fn.o

このファイルは次のように読むことができます:Cコンパイラgccを使用し、コンパイルオプション-Wallを使用して、オブジェクトファイル ‘main.o'と 'hello_fn.o'からターゲット実行可能なメインをビルドします(これらは順番にビルドされます)。 'main.c'と 'hello_fn.c'からの暗黙のルール)。 (4)rmコマンドで-f(force)オプションを指定すると、ファイルが存在しない場合はすべてのエラーメッセージが抑制されます。

makefileを使用するには、makeと入力します。 引数なしで呼び出されると、makefileの最初のターゲットがビルドされ、実行可能ファイル ‘main'が生成されます。

$ make
gcc -Wall   -c -o main.o main.c
gcc -Wall   -c -o hello_fn.o hello_fn.c
gcc   main.o hello_fn.o   -o main
$ ./main
Hello, world!

ソースファイルを変更した後に実行可能ファイルを再構築するには、単にmakeと入力します。 ターゲットファイルと依存ファイルのタイムスタンプをチェックすることにより、makeは変更されたファイルを識別し、ターゲットの更新に必要な中間ファイルを再生成します。

$ emacs main.c  (edit the file)
$ make
gcc -Wall   -c -o main.o main.c
gcc    main.o hello_fn.o   -o main
$ ./main
Hello, everyone!

最後に、生成されたファイルを削除するには、make cleanとタイプします。

$ make clean
rm -f main main.o hello_fn.o

より洗練されたメイクファイルには、通常、インストール(make install)とテスト(make check)のための追加のターゲットが含まれています。

このマニュアルの残りの部分の例は、makefileを必要としないほど十分に小さいですが、makeを使用することは、どのような大きなプログラムでも推奨されます。

2.7外部ライブラリとのリンク

ライブラリとは、プログラムにリンクできるプリコンパイルされたオブジェクトファイルの集合です。ライブラリの最も一般的な使用法は、C数学ライブラリにある平方根関数sqrtなどのシステム関数を提供することです。

ライブラリは通常、拡張子 ‘.a'を持つ特別なアーカイブファイル(静的ライブラリと呼ばれます)に格納されます。それらは別のツールであるGNU archiver arを使ってオブジェクトファイルから作成され、コンパイル時に関数への参照を解決するためにリンカーによって使用されます。 arコマンド(第10章コンパイラ関連ツール参照)を使ってライブラリを作成する方法については、後で説明します。簡単にするために、このセクションでは静的ライブラリのみを扱います。共有ライブラリを使用して実行時の動的リンクについては、次の章で説明します。

標準のシステムライブラリは通常、 ‘/ usr / lib'と ’/lib'というディレクトリにあります。(5)たとえば、Cのmathライブラリは、通常Unix上のファイル ‘/usr/lib/libm.a'に保存されています同様のシステム。このライブラリの関数の対応するプロトタイプ宣言は、ヘッダーファイル ’/usr/include/math.h'にあります。 C標準ライブラリ自体は ‘/usr/lib/libc.a'に格納され、 'printf'などのANSI / ISO C標準で指定された関数を含んでいます。このライブラリはすべてのCプログラムにデフォルトでリンクされています。

数学ライブラリ ‘libm.a'の外部関数sqrtを呼び出すプログラムの例を次に示します。

#include <math.h>
#include <stdio.h>

int
main (void)
{
  double x = sqrt (2.0);
  printf ("The square root of 2.0 is %f\n", x);
  return 0;
}

このソースファイルだけから実行可能ファイルを作成しようとすると、コンパイラはリンク段階でエラーを出します。

$ gcc -Wall calc.c -o calc
/tmp/ccbR6Ojm.o: In function `main':
/tmp/ccbR6Ojm.o(.text+0x19): undefined reference 
  to `sqrt'

問題は、外部の数学ライブラリ ‘libm.a'なしではsqrt関数への参照を解決できないことです。 関数sqrtは、プログラムまたはデフォルトライブラリ 'libc.a'に定義されておらず、明示的に選択されていない限り、コンパイラはファイル 'libm.a'にリンクしません。 なお、エラーメッセージ ’/tmp/ccbR60jm.o'に記載されているファイルは、リンク処理を行うために、 ‘calc.c'からコンパイラが作成した一時オブジェクトファイルです。

コンパイラがsqrt関数をメインプログラム ‘calc.c'にリンクできるようにするには、ライブラリ 'libm.a'を指定する必要があります。 これを行うための明白であるが面倒な方法の1つは、コマンドラインで明示的に指定することです。

$ gcc -Wall calc.c /usr/lib/libm.a -o calc

ライブラリ ‘libm.a'には、sin、cos、exp、log、sqrtなどのすべての数学関数のオブジェクトファイルが含まれています。 リンカーはこれらを検索し、sqrt関数を含むオブジェクトファイルを検索します。

sqrt関数のオブジェクトファイルが見つかると、メインプログラムをリンクして完全な実行可能ファイルを生成することができます。

$ ./calc 
The square root of 2.0 is 1.414214

実行可能ファイルには、main関数のマシンコードとsqrt関数のマシンコードが含まれ、ライブラリ ‘libm.a'の対応するオブジェクトファイルからコピーされます。

コマンドラインで長いパスを指定する必要を避けるために、コンパイラはライブラリとリンクするためのショートカットオプション ‘-l'を提供しています。 たとえば、次のコマンド、

$ gcc -Wall calc.c -lm -o calc

完全なライブラリー名 ‘/usr/lib/libm.a'を使用した上記の元のコマンドと同等です。

一般に、コンパイラ・オプション-lNAMEは、オブジェクト・ファイルを標準ライブラリ・ディレクトリのライブラリ・ファイル ‘libNAME.a'にリンクしようとします。 コマンドラインオプションと環境変数で追加のディレクトリを指定することができます。これについては後ほど説明します。 大規模なプログラムは通常、数学ライブラリ、グラフィックスライブラリ、ネットワーキングライブラリなどのライブラリをリンクするために、多くの-lオプションを使用します。

2.7.1ライブラリのリンク順

リンカーの従来の動作は、コマンドラインで指定されたライブラリの左から右に外部関数を検索することです。 つまり、関数の定義を含むライブラリは、それを使用するソースファイルまたはオブジェクトファイルの後に現れなければなりません。 これには、次のコマンドに示すように、ショートカット-lオプションで指定したライブラリが含まれます。

$ gcc -Wall calc.c -lm -o calc   (correct order)

リンカによっては、反対の順序(それを使用するファイルの前に-lmオプションを置く)はエラーになりますが、

$ cc -Wall -lm calc.c -o calc    (incorrect order)
main.o: In function `main':
main.o(.text+0xf): undefined reference to `sqrt'

‘calc.c'の 後ろsqrtに ライブラリやオブジェクトファイルが含まれていないためです。このオプションはファイル'calc.c'の後に現れ ます。 -lm

複数のライブラリを使用している場合は、ライブラリ自体についても同じ規則を遵守する必要があります。別のライブラリで定義された外部関数を呼び出すライブラリは、その関数を含むライブラリの前に現れます。

たとえば、GNU線形プログラミングライブラリ'libglpk.a ‘を使用するプログラム'data.c'は、数学ライブラリ'libm.a'を順番に使用して コンパイルする必要があります。

$ gcc -Wall data.c -lglpk -lm

‘libglpk.a'の オブジェクトファイルは'libm.a'で定義された関数を使用するから です。

現在のリンカーのほとんどは、順序にかかわらずすべてのライブラリを検索しますが、これを行わないライブラリもありますので、ライブラリを左から右に並べるという規約に従うのが最善です。

定義されていない参照で予期しない問題が発生し、必要なすべてのライブラリがコマンドラインに表示されているように見える場合は、これを覚えておいてください。

参考

An Introduction to GCC - Table of Contents