C言語のコンパイルにおけるアセンブラ→実行ファイルまでの流れをまとめてみた

備忘録として、C言語の コンパイルの流れをまとめてみた。

C言語の コンパイラの大まかな流れ

簡単なプログラムであれば gcc test.cだけで コンパイルが可能だが、実際の コンパイルは以下の4つの手順を踏んでいる。

  1. プリプロセッサで ソースコードを コンパイラが解釈できるように直す
  2. 1で作った ソースコードを アセンブラに直す
  3. 2のコードをオブジェクトファイル( 機械語)に直す
  4. 実行ファイルに直す(exeとかoutとか)

以下は、詳しく見ていく。

1、 プリプロセッサで ソースコードを コンパイラが解釈できるように直す。

プリプロセッサとは、C言語の ソースコードを コンパイラが解釈できるように書き直すことだ。

例えば、 C言語のコードには#includeや#defineなどのディレクティブ(Directive。日本語で「意味」と言う言葉)が用意されているが、 プリプロセッサを行うことで「define MAXのMAXはこういう意味だよ」とか「includeで指定したファイルも読み込んでね」と言う風に コンパイラに教えることができ、これらの情報を基に コンパイラが コンパイルできるような ソースコードに作り直している。

今回の例では、以下のtest.cファイルを用意した。hello worldを表示するための簡単なコードだ。

#include  <stdio.h>

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

上記のファイルを使って、コマンドラインで以下の様に書いて、プリプロセッサを行う。

gcc -E test.c

すると、以下の様な巨大な ソースコードが作られる。(以下の例は結構省略したヤツ。)

# 1 "test.c"
# 1 "<built -in>"
# 1 "<command -line/>"
# 1 "test.c"
# 1 "c:\\mingw\\include\\stdio.h" 1 3
# 38 "c:\\mingw\\include\\stdio.h" 3
# 39 "c:\\mingw\\include\\stdio.h" 3
# 56 "c:\\mingw\\include\\stdio.h" 3
# 1 "c:\\mingw\\include\\_mingw.h" 1 3
# 55 "c:\\mingw\\include\\_mingw.h" 3
# 56 "c:\\mingw\\include\\_mingw.h" 3
# 66 "c:\\mingw\\include\\_mingw.h" 3
# 1 "c:\\mingw\\include\\msvcrtver.h" 1 3
# 35 "c:\\mingw\\include\\msvcrtver.h" 3
# 36 "c:\\mingw\\include\\msvcrtver.h" 3
# 67 "c:\\mingw\\include\\_mingw.h" 2 3
</built>

上記のコードを生成することで、 コンパイラが C言語を解釈できるようになる。

参考記事:プリプロセッサでプログラムの質を向上させよう (1/4):目指せ! Cプログラマ(16) - @IT

参考記事:C言語 プリプロセッサ

また、 gcc -E の -Eの部分の意味は定かではないが、おそらくexpand(拡大する)と言う意味ではないか、と言う意見がある。(ソースが少なすぎて困った)

参考記事:c - What does gcc -E mean? - Stack Overflow

2、1で作った ソースコードを アセンブラに直す

1の手順で プリプロセッサを行った後は、 アセンブラファイルに書き直す。(別に1の手順を踏むことは必須ではない。1の手順を省略していきなり アセンブルしても、 コンパイラが自動で プリプロセッサを行ってくれるからだ)

gcc -S  test.c

上記の手順でtest.sと言う アセンブラファイルが生成される。

.file   "test.c"
.def    ___main;    .scl    2;  .type   32; .endef
.section .rdata,"dr"
LC0:
.ascii "hello world\0"
.text
.globl  _main
.def    _main;  .scl    2;  .type   32; .endef
_main:
LFB10:
.cfi_startproc
pushl   %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl    %esp, %ebp
.cfi_def_cfa_register 5
andl    $-16, %esp
subl    $16, %esp
call    ___main
movl    $LC0, (%esp)
call    _printf
movl    $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE10:
.ident  "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
.def    _printf;    .scl    2;  .type   32; .endef

gcc -Sの-Sの意味は、「Strip the outputでは?(outputを引きちぎる)」と言う意見もある。(ソースが少ない)

参考記事:what does the option -s of gcc mean ?

3, 2のコードをオブジェクトファイル( 機械語)に直す

アセンブラファイルが生成された後は、以下のようにコマンドを書く。

as  -o test.o test.s

これでtest.oと言うオブジェクトファイル( 機械語)が生成される。 また、ここでは アセンブリをしたいので gcc ではなく as で実行する。

4、実行ファイルに直す(exeとかoutとか)

最後にオブジェクトファイルを実行ファイルに直す。

gcc -o test test.o

これで Windowsであればtest.exe、 Linuxであればtest.outが生成されてプログラムを実行できる。

gccには他にも色々なオプションが用意されているので、暇な時にチェックしよう。

参考記事:gcc

参考記事:Using the GNU Compiler Collection (GCC): Overall Options