計画と改善

エンジニアのブログ

コンパイルからプログラム実行ファイル生成までの流れ(C言語)

データベース使用するライブラリを動作させるためにC言語コンパイラをインストールしなければならないことがあり、これまでよりも低レイヤーの学習しておこうという気になった。 今回はC言語本格入門のコンパイルからプログラム実行ファイル生成までの流れをまとめておく。

環境

Debian GNU/Linux 11 (bullseye)

GCC

C言語コンパイラgccは単にコンパイルをするのではなく、中で色々な処理を行なっている。 その処理とはプリプロセス、コンパイルアセンブル、リンクという流れで行っている。 プリプロセスはその名の通り、コンパイルの前処理。 コンパイルとはソースコードアセンブリ言語ソースコードに変換する動作。

拡張子の整理

  • .sで終わるファイル
  • .oで終わるファイル
    • 機械語に変換したファイル(リンク前)
  • .outで終わるファイル
    • リンク済みの実行ファイル

コンパイルアセンブリ言語ソース)への変換

nop.c(空のC言語処理を書いたファイル)を用意する

root@xxx:~/studyc# gcc -S nop.c

上記のようにコマンドを打つとnop.sが生成される。環境によって生成されるコードは異なる。

これは単なるアセンブリ言語ソースコード変換となる。

.file    "nop.c"
    .text
    .globl   main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq  %rsp, %rbp
    .cfi_def_cfa_register 6
    movl %edi, -4(%rbp)
    movq  %rsi, -16(%rbp)
    movl $0, %eax
    popq %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident   "GCC: (Debian 10.2.1-6) 10.2.1 20210110"
    .section .note.GNU-stack,"",@progbits

プリプロセス処理の確認

以下のコマンドを打つとプリプロセスした結果をテキストファイルに保存する

root@xxx:~/studyc# gcc -E nop.c > nop.txt

nop.txtの中身をcatするなどして確認するとnop.cに記載されていたはずの#include <stdio.h>の記述が消えている。これはプリプロセスの結果加工され、stdio.hの内容を表示させているため。このstdio.h自体は以下のように/usr/includeに入っていることが確認できる。

root@xxx:/usr/include# find . -name stdio.h
./x86_64-linux-gnu/bits/stdio.h
./stdio.h
./c++/10/tr1/stdio.h

アセンブル機械語の生成)

gccコマンドをそのまま使うと実行ファイルの生成まで行ってしまうが、gcc -cコマンドを使うとリンクまでは行わないでおける。(実行ファイルを生成せずに機械語の生成にとどめる)

root@xxx:~/studyc# gcc -c nop.s

こうするとnop.oが生成される。

root@xxx:~/studyc# ls
nop.c  nop.o  nop.s  nop.txt

実行しようとすると

root@xxx:~/studyc# ./nop.o
bash: ./nop.o: cannot execute binary file: Exec format error

実行できない。これはnop.o機械語ではあるが、実行するための情報やC言語でプログラムを動かすための情報が不足しているため。 ここでnop.oの容量を確認しておく

root@xxx:~/studyc# ls -l nop.o
-rwxr-xr-x 1 root root 1232 Sep 19 07:43 nop.o

リンカ

これらはリンクを行うことで、自動で付加されて機械語のファイル(実行ファイル)になる。この実行できない機械語のファイルをオブジェクトファイルという。 オブジェクトファイルはDebian系では以下のようにリンクを使って実行ファイルを作成できる

root@xxx:/lib/x86_64-linux-gnu# cd ~/studyc
root@xxx:~/studyc# ld nop.o -dynamic-linker /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/{crt1.o,crti.o,crtn.o} -lc

実行ファイルの容量を見るとかなり増えていることがわかる。 この増えた分が実行ファイルのための情報や、C言語プログラムを動かすための情報になる。

root@xxx:~/studyc# ls -l a.out
-rwxr-xr-x 1 root root 15232 Sep 19 07:56 a.out

スタートアップルーチン

リンクにより増えた実行ファイルの中で、C言語でプログラムを動かすための情報をスタートアップルーチンと呼ぶ。 このスタートアップルーチンが含まれたファイルをCランタイムと呼び、プログラムが動作するためにはCランタイムの他にlibcと呼ばれるC言語で書いたプログラムを動かすためのライブラリが必要。 libcには画面に文字を表示するなどに必要なプログラムが格納されている。リンクという作業はこれらを結合するための作業なのでリンクと呼ぶ。 この記事のリンカセクションで実行したコマンドの中で、crtから始まるファイルがスタートアップルーチンを含んでいるファイル。 main関数はスタートアップルーチンから呼び出される。