👨‍🔧

gccの処理の流れを知る

2021/06/06に公開

はじめに

対象とする読者

gcc(g++も含む)を既に使用している方。
gccを実行した際の処理の流れをざっくり知りたい方。

記事で説明する事

実際のソースコードを例にして、C/C++のソースコードが実行ファイルに変換されるまでの流れを説明します。
gccを主語として説明しますが、この記事で説明する内容はg++にも共通するものです。

gccの処理の流れ

gccの内部処理

gccの処理は、大きく次の3ステップに分けることができます。
(1)プリプロセス
(2)コンパイル(+アセンブル)
(3)リンク

プリプロセス

プリプロセスは、#から始まる行を処理します。#で扱うのは主に文字列の置換作業なので、複雑な処理はありません。
通常ファイルに出力される事はありませんが、保存する場合の拡張子は.i(C言語)、.ii(C++)を指定することが慣習となっています。

コンパイル

コンパイルではプログラミング言語の機械語への変換を行います。この処理には非常に多くのオプションを指定することができます。どのようなコードに対して、警告を出すか、エラーにするのかを設定する-W系オプション。コードを最適化する-O系オプションが特によく使用されます。
コンパイルが成功すると.oファイルが出力されます。このファイルのことをオブジェクトファイルと呼びます。オブジェクトファイルは、CPUやOSに依存する形式になっていますので、異なる環境で使用することはできません。
※これらの処理をコンパイル+アセンブルと表現することもできますが、説明の簡単化のためアセンブルはここでは省略します。

リンク

リンクでは複数のオブジェクトファイルをつなぎ合わせ、単一のプログラムとして実行可能な状態に変換します。コンパイルまではソースファイル単独で実行しています。そのため、参照している他のソースファイルの事は知りません。リンクでは、それぞれのファイルが参照している変数や関数などに不整合がないかを確認します。
リンクには2種類があります。オブジェクトを単に指定した場合は静的リンクになります。

静的リンク

参照元のファイルに参照先のファイルをつなぎ合わせて1つのファイルにまとめます。

動的リンク

ファイルは分けた状態のまま、参照先の情報のみをファイルに記録します。

コードを使用した解説

ここから、実際のソースコードを例に、gcc実行時の動きを説明します。

ソースコード

以下の簡単なC言語のソースコードを使用します。
ファイルは同一ディレクトリに配置されているものとします。

main.c
#include "func.h"
int main()
{
    func();	
}
func.h
void func(void);
func.c
#include <stdio.h>
void func(void)
{
    printf("Hello!\n");
}

gccの通常実行

gccの引数にファイル名を指定実行すると、以下のように実行可能なプログラムa.outが出力されます。

$ gcc main.c func.c
$ ./a.out
$ Hello!

このように、何もオプションをつけないとプリプロセス~リンクまでの処理が一気に行われます。

プリプロセスを行う

gccでプリプロセス後に処理を止めるためには以下のようにします。

$ gcc -E main.c > main.i
$ gcc -E func.c > func.i 

main.iは#行が展開され次のような結果になります。includeで指定したファイルの中身が展開されていることがわかります。プリプロセス後の#はデバッグに使われるコメントなのでプログラムの動きには影響しません。

main.i
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "main.c"
# 1 "func.h" 1
void func(void);
# 2 "main.c" 2
int main()
{
    func();
}

func.iの結果はstdio.hの中身が展開されるため非常に長くなります。ここでは省略しますので、気になる方は試してみてください。

コンパイルを行う

gccでコンパイルで処理を止めるためには以下のようにします。
ここでは、iファイルを入力していますが、cファイルにしても結果は同じです。
成功すると、オブジェクトファイル(.o)が出力されます。

$ gcc -c main.i
$ gcc -c func.i

リンクを行う

gccでリンクを行うには以下のようにします。gccの処理を途中で止めるわけではないのでオプションの指定は必要ありません。

$ gcc main.o func.o

静的リンクが実行され、a.outが出力されます。

まとめ

gccがC/C++のコードを実行可能なプログラムに変換するまでの流れを解説しました。
各ステップの詳細に関しては、別途記事を書きたいと思います。

Discussion