個人的な備忘録です。
間違いがありましたらコメントいただけるとありがたいです。
参考サイト
gccコンパイラの使い方
江添亮のC++入門
リンカの役割 自分メモメモ
[C言語] 共有ライブラリと動的ライブラリを整理する
実行環境
- Ubuntu 16.04 LTS
- gcc 5.4
a.outを作る
$ gcc main.c
gcc が main.c を処理し、 a.out という実行ファイルが作られる。
実行ファイルに名前をつける
$ gcc main.c -o main
実行ファイルに名前をつけるときは -o オプションを使う。
実行後、 main という名前の実行ファイルが作られる。
複数のファイルから実行ファイルを作る
$ gcc main.c hello.c -o main
main.c hello.c という2つのファイルから main という実行ファイルを作る。
※ 例
# include <stdio.h>
void f();
int main() {
f();
}
# include <stdio.h>
void f() {
printf("hello\n");
}
上記のコマンドでこの2つのファイルをコンパイルして、 ./main を実行すると
$ hello
と出力される。
オブジェクトファイルを作る
オブジェクトファイルはコンパイルはしたがリンクが終わっていない状態のファイルのこと。
gccでは以下のステップを踏んで実行ファイルが作られる。
- プリプロセッサ
- コンパイルを行う前にソースコードに対して行われる前処理
- c/c++では, ディレクティブ(
# ...)という命令を処理する。
# include
# define
# if
# ifndef
# elif
# endif
などなど...
-
コンパイル
- ソースコードをチェックして、中間言語や機械語に翻訳する。
-
リンク
- オブジェクトファイルやライブラリを連結して、実行ファイルを作る。
例えば、 hello.c というファイルをオブジェクトファイルにするときは -c オプションを使用する。
$ gcc -c hello.c
これを実行することで hello.o というオブジェクトファイルが作られる。
もちろん複数ファイルも指定できる。
$ gcc -c a.c b.c
これで a.o b.o ができた。
複数のオブジェクトファイルから実行ファイルを作る
a.c b.c c.c というソースコードから以下のコマンドを使用して main という実行ファイルを作るとする。
$ gcc a.c b.c c.c -o main
その後、 a.c を変更したので main を更新したくなった。
しかし、main を更新するために上記コマンドを実行すると変更していない b.c c.c も再びコンパイルすることになり二度手間である。
そういうときにはすでにコンパイル済みのオブジェクトファイルを使うことができる。
$ gcc a.c b.o c.o -o main
これで実行ファイル main ができた。
静的ライブラリをリンクする -L
静的ライブラリとは libxxx.a のようなファイルのこと。
オブジェクトファイルの集合体で、 ar コマンドを使用して以下のように作成する。
ar -r libsample.a a.o b.o c.o
※ nm コマンドとか使うと色々中身が見れる。aファイル作成前のoファイルと比較すると面白いかも?
静的ライブラリもオブジェクトファイルの集合体だから、実行ファイルを作成するときに使用することができる。
ただしコンパイル時に静的ライブラリがあるディレクトリを -L オプションで指定する必要がある。
例えば、
---
|- a.c
|- b.c
|- libsample.a
というディレクトリ構成で, a.c b.c libsample.a から実行ファイル main を作るときは以下のようにしてディレクトリと静的ライブラリを指定する。
$ gcc a.c b.c -L. -lsample -o main
静的ライブラリは -lxxx の形で指定する。lib、.a の部分は省略することができる。
(ここから少し怪しいです...)
-Lで静的ライブラリが存在するディレクトリを指定すると書いたが、 算術用ライブラリ libm.a は
$ gcc a.c -lm -o main
のように -L オプションなしで使用できる。
これは、リンカがデフォルトでライブラリを探しにいくディレクトリが決まっているからである。
これは ld --verbose でリンカスクリプトを見ると書いてある。
参考: リンカの役割 自分メモメモ
ヘッダを追加 -I
c/c++言語でヘッダファイルをインクルードする際には #include ディレクティブを使用する。
このときヘッダファイルの指定には以下の2つ方法がある。
# include <stdio.h>
# include "stdio.h"
<...> と "..." の違いはコンパイラがヘッダファイルを探しにいくディレクトリのパスである。
<...> はデフォルトで指定されているパスを参照してヘッダファイルを探す。
"..." はデフォルトで指定されているパスに加えて "..." 内に書かれたパスにもヘッダファイルを探しにいく。
コンパイラがデフォルトで参照するパスは -v オプションを使用して、コンパイル処理の各段階において実行されたコマンドを出力することによって確認するのが手っ取り早い。
例えば、次の出力は ubuntu 16.04LTS で gcc -v xxx.c を実行させたときの一部である。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86-64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
... 省略 ...
# include "..." search starts here:
# include <...> search starts here:
/usr/include/c++/5
/usr/include/x86_64-linux-gnu/c++/5
/usr/include/c++/5/backward
/usr/local/include
... 省略 ...
しかし、毎回ヘッダファイルの指定に #include "..." を使うのはパスをハードコーディングすることになり、色々面倒だ。
その時に使用するのが -I オプションである。
-I オプションを使用すると、 コンパイラが参照するパスに -I で指定したパスを追加してくれる。
次の出力は、 gcc -I../ -v xxx.c を実行したものである。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86-64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
... 省略 ...
# include "..." search starts here:
# include <...> search starts here:
../
/usr/include/c++/5
/usr/include/x86_64-linux-gnu/c++/5
/usr/include/c++/5/backward
/usr/local/include
... 省略 ...
しっかりと <...> にパスが追加されている。
これで #include <...> で自作のヘッダが追加できるようになった。
-Wall, -W
- バグかもしれない部分に警告を出してくれる。
- 潜在的なミスを指摘してくれるので基本的に付けておいた方が得な気がしている。
- 参考: gcc の警告オプション -Wall と -W
-std=c++11, 14, 17, ...
- それぞれ, c++11, c++14, c++17の機能を有効にする。
- C++は3年に1回新しい国際基準が更新されるため、C++20なら, -std=c++20のように追加されていくでしょう。
- 参考: 処理系
-O1, -O2, -O3, -Os
- 最適化オプション
- 参考: 最適化オプション
- 参考: プログラム高速化の禁術
まとめ
CMakeとかは偉大だなあって思う...