gccコンパイラの使い方
C言語のソースコードは、そのままでは実行できません。パソコンのCPUが実行できる形式に変換する必要があります。C言語では、ソースコードの変換にCコンパイラ(コンパイラ)を使用します。Cコンパイラは、いろいろなコンパイラがありますが、ここでは、GNU の gcc コンパイラの使い方を紹介します。gcc を対象にしていますが、ここで扱うテーマは広く使用されているものであるため、clang でも通用します。
読み方
- gcc
- じーしーしー
目次
概要
C言語のソースコードは、そのままでは、パソコンは実行できません。 Cコンパイラは、ソースコードを実行可能な形式に変換します。 変換されてできたファイルは、オブジェクトファイル と呼ばれます。
Unix (LinuxやFreeBSDなど) で実行できるオブジェクトファイル(実行ファイル)は、ELF フォーマットです。 Unix で gcc コマンドを使うと、C言語のソースコードは、ELF形式のファイルに変換されます。
gccには、ものすごいたくさんのオプションがあります。 ここでは、主要なオプションや使い方を紹介します。
だいたい、以下のものをテーマとして扱います。
- コンパイル
- ヘッダファイル
- ライブラリ
- 最適化
- プリプロセッシング
- アセンブル
インストール
OSによって、はじめからコンパイラがインストールされている場合もありますし、自分でインストールしないといけない場合もあります。
$ pkg install gcc5
コンパイラのコマンド名
gcc コンパイラは、おそらく gcc もしくは、 gcc5 のようにバージョン名付きのファイル名です。 gcc コンパイラは、/usr/bin か /usr/local/bin などのディレクトリにインストールされます。 環境によっては、cc コマンドが gcc コマンドであることもあります(場合によっては、clangかもしれません)。
- gcc
- gcc5
- cc
私の個人的なFreeBSDの環境だといろいろな gcc が同居しています。
$ ls /usr/local/bin/gcc* /usr/local/bin/gcc-ar49* /usr/local/bin/gcc-ranlib5* /usr/local/bin/gcc-ar5* /usr/local/bin/gcc46* /usr/local/bin/gcc-nm49* /usr/local/bin/gcc49* /usr/local/bin/gcc-nm5* /usr/local/bin/gcc5* /usr/local/bin/gcc-ranlib49*
コンパイルして実行ファイルを作成
C言語のソースコードをコンパイルして、実行ファイルを作成する、一番簡単な例は以下の通りです。
$ gcc main.c
ターゲット(作成するオブジェクトファイル)を指定しない場合、 a.out と呼ばれるファイルがカレントディレクトリに作成されます。
複数のファイルをコンパイルする
C言語のソースコードは、複数のファイルに分割することができます。 複数のファイルがある場合は、複数のファイルを指定します。
$ gcc main.c func.c
ヘッダファイルのあるディレクトリを指定する
C言語では、 .c と呼ばれるファイル以外に、ヘッダファイルと呼ばれるソースコードを扱います。 標準的なヘッダファイルは、 Unix の場合 /usr/include といったディレクトリに置かれています。
よく使われる stdio.h や stdlib.h は、 /usr/include にあります。 /usr/include は、コンパイラで指定せずとも、自動的に検索されます。
システムによりますが、追加でインストールしたライブラリは /usr/local にインストールされるケースがあります。 /usr/local にインストールされたライブラリのヘッダファイルは、一般的には /usr/local/include に格納されます。
コンパイラは、 include ディレクティブを見つけると、自動的に標準的のヘッダファイルのディレクトリを探しに行きます。
#include <stdio.h>
という行にコンパイラが遭遇すると、 /usr/include を探しにいき、 /usr/include/stdio.h を発見して、stdio.hの内容を取り込みます。
/usr/local/include といったディレクトリは、デフォルトの状態では、検索しにいきません。 そのため、標準以外のディレクトリのヘッダファイルを読み込むためには、コンパイラにディレクトリを指定しなければなりません。
作成したソースコードのあるディレクトリなどにヘッダファイルをおいている場合には、カレントディレクトリを指定しなければなりません。
include ディレクトリの指定には、 -I オプションを使用します。このオプションは、同じコマンドラインで、何度も指定できます。そのため、複数のディレクトリを指定したい場合には、 -I オプションを複数回指定します。
/usr/local/include にあるヘッダファイルを読み込みたい場合は、以下の通りです。
$ gcc -I /usr/local/include main.c
カレントディレクトリ にあるヘッダファイルを読み込みたい場合は、以下の通りです。
$ gcc -I . main.c
/usr/local/include とカレントディレクトリにあるヘッダファイルを読み込みたい場合は、以下の通りです。
$ gcc -I /usr/local/include -I . main.c
ライブラリをリンクする
ライブラリを使用する場合には、ライブラリをリンクしなければなりません。 ただし、libc と呼ばれる標準的なライブラリは、デフォルトでリンクされるため、明示的にリンクする必要はありません。
たとえば、数学ライブラリである math を利用する場合には、明示的にコンパイラにリンクの指定をしなければなりません。
$ gcc -lm main.c
リンクされているかどうか、確認するには、ldd コマンドを使うと簡単にわかります。
$ gcc a.c $ ldd a.out a.out: libc.so.7 => /lib/libc.so.7 (0x80081b000) $ gcc a.c -lm $ ldd a.out a.out: libm.so.5 => /lib/libm.so.5 (0x80081b000) libc.so.7 => /lib/libc.so.7 (0x800a41000)
標準以外のライブラリをリンクする
include と同様に 標準以外のライブラリをリンクしないとならない場合には、サーチパスを指定しないといけません。 大抵の場合は、追加のライブラリは /usr/lib でなければ、/usr/local/lib などにインストールされます。
もし、/usr/local/lib に libcurl のライブラリがインストールされているとしたら、libcurl をリンクする場合には、
- ライブラリのディレクトリ /usr/local/lib を指示するために -L/usr/local/lib
- ライブラリをリンクする指示 -lcurl
- ライブラリのヘッダファイルのディレクトリを指定するために -I/usr/local/include
の3つを指定します。
$ gcc -I/usr/local/include -L/usr/local/lib -lcurl main.c
オブジェクトファイル(.o)を作成する
実行ファイルではない、オブジェクトファイルを作成する場合は、 -c オプションを指定します。
$ gcc -c hello.c
-c オプションは、ソースコードをコンパイル、もしくは、アセンブルしますが、リンクを実行しません。リンキングステージは、完了していません。
小さなプログラムであれば、.o ファイルを作成するメリットはないかもしれませんが、大規模なプログラムであれば、ファイルを分割して、オブジェクトファイルを作成するメリットがあります。複数のソースコードが存在するときに、1つ1つを .o にしておくと、再コンパイル時にコンパイル時間を省略できます。
たとえば、以下のようにすると、a.o , b.o が作成できます。
$ gcc -c a.c b.c
a.o か b.o のどちらかに main 関数がある前提(つまり gcc a.c b.c が成り立つという前提)で、以下のコマンドを実行すると a.out が作成されます。
$ gcc a.o b.o
手動で gcc を叩く場合に、どの .c が更新されたかを考えるのは面倒です。ですが、そういった処理は、 make といったコマンドに任せることができます。 make と呼ばれるコマンドを利用して、.o ファイルを使用したコンパイルを行う場合、make によって、 .o と .c のタイムスタンプを見て、再コンパイルが必要かどうかをチェックし、 .o の方が新しければ、コンパイルをしない、といった処理が行われます。 ただし、この処理を行うには、Makefile にちゃんと指定しないといけません。
-cオプションで作成された.oとELFの違い
-c なしで作成された実行ファイルと -c オプションを使用して作成したオブジェクトファイルは、どのような違いがあるのでしょうか?
gcc a.c -o a.out gcc -c a.c -o a.o
ファイルコマンド(file)で単純に比較すると以下の通りです。
a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (FreeBSD), not stripped a.out: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked (uses shared libs), for FreeBSD 10.0 (1000510), not stripped
a.o は、リロケータブルなファイルとなっています。 a.out は、実行可能なファイルとなっていることがわかります。
作成するファイル名を指定する
コンパイラが作成するファイル名を指定することができます。
ファイル名の指定には、 -o オプションを使用します。 以下のコマンドは、hello という実行ファイルを作る例です。
$ gcc hello.c -o hello
オブジェクトの最適化
コンパイラは、コンパイルするときに、最適化処理を行います。 最適化レベルを上げると、コンパイルの時間が増える場合もありますが、その分、プログラムの実行時間が短縮できるかもしれません。
最適化オプションには、以下のものがあります。
- -O
- -O1
- -O2
- -O3
- -Os
-O0 (オーゼロ)は、コンパイル時間を短くします。 -Os は、サイズのための最適化です。コードサイズを増やす最適化は行いません。
使用例は、以下の通りです。
* gcc -O2 main.c * gcc -Os main.c
アセンブリ言語に変換する
C言語のソースコードからオブジェクトファイルを作成するのではなく、アセンブリ言語に変換することもできます。
$ gcc -S hello.c
上記のコマンドで、 hello.s というファイルが作成されます。
/* * hello.c * Copyright (C) 2016 kaoru <kaoru@localhost> * * Distributed under terms of the MIT license. */ #include <stdio.h> int main(int argc, char *argv[]) { puts ("Hello world"); return 0; }
から
.file "hello.c" .section .rodata .LC0: .string "Hello world" .text .globl main .type main, @function main: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl %edi, -4(%rbp) movq %rsi, -16(%rbp) movl $.LC0, %edi call puts movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size main, .-main .ident "GCC: (FreeBSD Ports Collection) 5.3.0" .section .note.GNU-stack,"",@progbits
が作成されます。
プリプロセッサを実行した結果を得る方法
コンパイルには、いくつかのステージがあります。 コンパイラは、C言語のソースコードをコンパイルするときに、プリプロセッサディレクティブを処理するステージがあります。 このステージでは、 include や define などの展開を行います。
コンパイルがうまくいかない場合やプログラムが正しく動かないときに、何が問題なのかを調査する方法の1つとして、プリプロセッサディレクティブがどのように処理されたかを確かめるケースがあります。
-E オプションを指定した場合、プリプロセッシングステージの後にコンパイル処理をストップします。
以下のコマンドは、標準出力にプリプロセッシングの結果を出力します。
$ gcc -E a.c
関連項目
ツイート