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

ReratedPosts

C言語のfopen関数で「警告: 2 番目の ‘fopen’ の引数へ渡すときに整数からキャスト無しにポインタを作成しています [デフォルトで有効]」と言うエラーが出たときの対処法
なぜ子プロセスを呼び出した時にsetsid()を使うのか?
socketのgetaddrinfoの使い方や仕組みについてまとめてみる
ダブルポインタ(ポインタのポインタ)のメリットや使い道を紹介する
Linuxのopen(低水準入出力)の使い方やfopenとの違いをまとめていく
C言語の安全な文字列コピーは、strcpyよりもstrncpy、そしてstrlcpyが良い