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