基本を再チェック〜gcc〜

YAMAMORI Takenori ●yamamori

●コンパイル過程の詳細

ここでは,C/C++のソースから実行バイナリを作成することの全体を指して 広義的にコンパイルと呼んでいますが,このコンパイルは, プリプロセッサの処理・狭義のコンパイル・アセンブル・リンクという過程から 成り立っています.

gccを起動すれば,これらの必要な過程が自動的に実行されます. gccというのは,実は「ドライバ」と呼ばれるプログラムであり, gcc自身はコンパイルなどの実作業を何も行なわず, gccの内部から,実際にコンパイル・アセンブル・リンクなどを行なうコマンドを 呼び出すことによって一連のコンパイル動作を行なっているのです.(下図)

ここで,途中のcc1またはcc1plusの部分は, ソースファイルがC言語かC++かによって変わります. C++の場合は,gccで起動した場合でもcc1plusが呼び出されます.

●gcc内部から呼び出されるコマンドとその流れ
         +-----+
         | cpp | Cプリプロセッサ(「#include」「#define」などの展開)
         +-----+
           ↓
  ・--------------------・
  ↓                    ↓
+---------------+ +-------------+
| cc1           | | cc1plus     | コンパイラ本体
|(C言語の場合)| |(C++の場合)|  (アセンブラのコードを出力)
+---------------+ +-------------+
  ↓                    ↓
  ・--------------------・
           ↓
         +----+
         | as | アセンブラ(リンク前のオブジェクトファイル「*.o」を出力)
         +----+
           ↓
         +----+
         | ld | リンカ
         +----+  (「*.o」やライブラリをリンクし,実行バイナリを作成する)

○コンパイルの途中の段階で止めるには

このように複数の処理過程をもつコンパイル動作を, gccにオプションを付けることによって,途中の段階で止めることができます.

「-c」オプションを付けると, リンク前のオブジェクトファイルである「*.o」の段階で止まります. また,「-S」オプションを付けると, アセンブラのソースである「*.s」の段階で止まります.

「-c」オプションを付けてリンクの前の「*.o」のファイルの段階で止めることは, 複数の「*.c」や「*.cc」のソースファイルに分かれたプログラムを 分割コンパイルするためによく用いられます. Makefileを用いたmakeでは,「-c」オプションを付ける方式がメインになります.

一方「-S」オプションは,コンパイラの出力するアセンブラのソースを 検証してみたいような場合以外,あまり使われないでしょう.


○アセンブルやリンクの段階から始めるには

「*.s」や「*.o」のファイルの段階で止めたあと, 再度コンパイル作業の続きを行なうには, 再びgccコマンドを「*.s」や「*.o」のファイルに対して実行すればOKです. gccはファイルの拡張子から,入力されたファイルを自動的に判断し, asやldなどの実作業を行なうコマンドを内部で起動してくれます.

ところでasやldは,/usr/local/binまたは/usr/binなどの通常のPATHが通った ディレクトリにあり,asやldをgccを通さずに直接実行することもできます. しかし通常は,アセンブルもリンクもgccを通して実行した方が良いでしょう. 特にldの場合,gccを通して起動すれば, 通常のリンク作業で必要となるlibcなどのライブラリと, crt1.oなどのCラインタイムオブジェクトとリンクするためのオプションを 自動的に付けてくれます.

一方,cppやcc1・cc1plusは,/usr/local/lib/gcc-libまたは/usr/lib/gcc-lib あるいは/usr/libexecなどのディレクトリ以下にあり, これらをgccを通さずに直接起動することは通常はありません.

以上の,「-S」「-c」オプション関連のファイルの流れを下図にまとめます.

●「-S」「-c」オプションとファイルの流れ

+=================================================================+
| hello.c                                                         |
+=================================================================+
   ↓                              ↓                        ↓
+--------------------------+  +--------------------+  +--------------------+
| gcc -O2 hello.c -o hello |  | gcc -O2 -S hello.c |  | gcc -O2 -c hello.c |
+--------------------------+  +--------------------+  +--------------------+
   ↓                              ↓                        ↓
   ↓     +================================+                 ↓
   ↓     | hello.s                        |                 ↓
   ↓     +================================+                 ↓
   ↓        ↓                    ↓                        ↓
   ↓ +----------------------+  +----------------+           ↓
   ↓ | gcc hello.s -o hello |  | gcc -c hello.s |           ↓
   ↓ +----------------------+  +----------------+           ↓
   ↓        ↓                    ↓                        ↓
   ↓        ↓                 +=================================+
   ↓        ↓                 | hello.o                         |
   ↓        ↓                 +=================================+
   ↓        ↓                    ↓                      
   ↓        ↓                 +----------------------+
   ↓        ↓                 | gcc hello.o -o hello |
   ↓        ↓                 +----------------------+
   ↓        ↓                    ↓
+=============================================+
| hello                                       |
+=============================================+

○ファイル内容の移り変わり

ここでhello.cについて, コンパイル途中の各ファイルの中身の移り変わりを見てみましょう.(下図) hello.sのアセンブラのソースの段階まではテキストファイルであり, そのまま中身を表示することができます. それ以降のhello.oおよび実行バイナリのhelloは, バイナリファイルのため,GNU binutilsに含まれるobjdumpという, 一種の逆アセンブラを使って表示しています.

なお,ここではgccのオプションに,「-O2」の他に「-fomit-frame-pointer」を付け, アセンブラのコードがさらに小さくなるようにしています.

●ファイルの中身の移り変わり

 ▼hello.c
+--------------------------------+
|#include <stdio.h>              |
|                                |
|main()                          |
|{                               |
|  printf("Hello World !\n");    |
|  return 0;                     |
|}                               |
+--------------------------------+
             ↓
             ↓
 ▼hello.s   ↓
+-----------------------------------+
|.section        .rodata            |
|.LC0:                              |
|        .string "Hello World !\n"  |
|.text                              |
|        .align 4                   |
|.globl main                        |
|        .type    main,@function    |
|main:                              |
|        pushl $.LC0                |
|        call printf                |
|        xorl %eax,%eax             |
|        addl $4,%esp               |
|        ret                        |
+-----------------------------------+
             ↓
             ↓
  ▼hello.o(「objdump -d hello.o」で表示)
+-----------------------------------------------------+
|hello.o:     file format elf32-i386                  |
|                                                     |
|Disassembly of section .text:                        |
|                                                     |
|00000000 <main>:                                     |
|   0:   68 00 00 00 00          pushl  $0x0          |
|   5:   e8 fc ff ff ff          call   6 <main+0x6>  |
|   a:   31 c0                   xorl   %eax,%eax     |
|   c:   83 c4 04                addl   $0x4,%esp     |
|   f:   c3                      ret                  |
+-----------------------------------------------------+
             ↓
             ↓
  ▼hello(「objdump -d hello」で表示)
+---------------------------------------------------------------------+
|hello:     file format elf32-i386                                    |
|                                                                     |
|Disassembly of section .init:                                        |
|                                                                     |
|08048298 <_init>:                                                    |
| 8048298:       55                      pushl  %ebp                  |
|                :                                                    |
|                :                                                    |
| 80482c6:       c3                      ret                          |
|Disassembly of section .plt:                                         |
|                                                                     |
|080482c8 <.plt>:                                                     |
| 80482c8:       ff 35 64 94 04 08       pushl  0x8049464             |
|                :                                                    |
|                :                                                    |
| 8048313:       e9 b0 ff ff ff          jmp    80482c8 <_init+0x30>  |
|Disassembly of section .text:                                        |
|                                                                     |
|08048320 <_start>:                                                   |
| 8048320:       31 ed                   xorl   %ebp,%ebp             |
|                :                                                    |
|                :                                                    |
|                :                                                    |
|080483c8 <main>:                                                     |
| 80483c8:       68 30 84 04 08          pushl  $0x8048430            |
| 80483cd:       e8 36 ff ff ff          call   8048308 <_init+0x70>  |
| 80483d2:       31 c0                   xorl   %eax,%eax             |
| 80483d4:       83 c4 04                addl   $0x4,%esp             |
| 80483d7:       c3                      ret                          |
| 80483d8:       90                      nop                          |
|                :                                                    |
|                :                                                    |
|                :                                                    |
+---------------------------------------------------------------------+

○途中の実行過程を表示するには

gccに「-v」オプションを付けることによって, gcc内部から起動されている各種コマンドとそのオプションをすべて表示することができます. hello.cに対して「-v」オプションで表示させた例を実行例4に示します. かなり込み入っていますが,注意深く見ると,cpp・cc1・as・ldと順に実行されていることがわかります. 途中の段階のファイルは, /tmp/ccqGFzyj.sとか/tmp/ccSJw1AW.oのような一時的なファイル名がgccによって付けられていることもわかります.

●実行例 gcc内部から起動されるコマンドを表示
$ rm -f hello hello.o
$ make CC='gcc -v' hello
gcc -v -O2    hello.c   -o hello
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=91 -D__ELF__ -Dunix -Di386 -D__i386__ -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(posix) -D__OPTIMIZE__ -Asystem(unix) -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ hello.c /tmp/ccODRtXI.i      ← cppの実行
GNU CPP version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) (i386 Linux/ELF)
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /usr/i386-redhat-linux/include
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include
 /usr/include
End of search list.
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cc1 /tmp/ccODRtXI.i -quiet -dumpbase hello.c -O2 -version -o /tmp/ccqGFzyj.s           ← cc1の実行
GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) (i386-redhat-linux) compiled by GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release).
 as -V -Qy -o /tmp/ccSJw1AW.o /tmp/ccqGFzyj.s           ← asの実行
GNU assembler version 2.9.1 (i386-redhat-linux), using BFD version 2.9.1.0.24
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o hello /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib /tmp/ccSJw1AW.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o
                                                        ↑ ldの実行

To『基本を再チェック〜gcc〜』[index]


このページは、技術評論社 Software Design 2001年2月号、『プログラムのコンパイルとリンク』の原稿を元に、Web 用に再構成したものです。
To 謎の処理系 SunOS 4.1.4 with Linux/FreeBSD[Home]
yamamori