基本を再チェック〜gcc〜 |
YAMAMORI Takenori ●yamamori |
ここでは,C/C++のソースから実行バイナリを作成することの全体を指して 広義的にコンパイルと呼んでいますが,このコンパイルは, プリプロセッサの処理・狭義のコンパイル・アセンブル・リンクという過程から 成り立っています.
gccを起動すれば,これらの必要な過程が自動的に実行されます. gccというのは,実は「ドライバ」と呼ばれるプログラムであり, gcc自身はコンパイルなどの実作業を何も行なわず, gccの内部から,実際にコンパイル・アセンブル・リンクなどを行なうコマンドを 呼び出すことによって一連のコンパイル動作を行なっているのです.(下図)
ここで,途中のcc1またはcc1plusの部分は, ソースファイルがC言語かC++かによって変わります. C++の場合は,gccで起動した場合でもcc1plusが呼び出されます.
+-----+ | 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」オプション関連のファイルの流れを下図にまとめます.
+=================================================================+ | 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によって付けられていることもわかります.
$ 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の実行 |