概要
C/C++のビルドを理解するための入門記事です。
C/C++言語の入門書を一通り読み終わって「じゃあ結局どうすればいいんだ」となってる人向けです。
前書き
プログラミング言語を入門するとき、C言語から学んだ人なら次のようなソースコードを書いたことがあるのではないでしょうか?
/* おまじない */
#include <stdio.h>
int main(int argc, char** argv) {
printf("Hello, world!\n");
return 0;
}
多少の差異はあれ、いつものHelloWorldプログラムですね。
これを実行するとき、例えばLinux環境なら、
gcc main.cpp
とコマンドを打った後に、
./a.out
みたいな感じでプログラムを実行し、Hello, world!
と表示されることを確認すると思います。
入門書ではこの後変数とかifとかforとかいろいろな文法の書き方を確かめる処理がつらつらと書かれているはずです。
しかしながら、私が観測した限りの入門書ではこの先何をすればいいかを書いていないことが多いです。入門の後の応用ができないってやつです。残念ながらこれでは大規模なアプリケーションを開発しようと思ったときに作ることはできません。
次の項からC/C++における大規模なアプリの作り方について本格的に説明していきますが、HelloWorld以上の大きなプログラムを作るときに魂に刻み付けるべきとても重要な心構えがあります。ずばり
自分一人で全部作ってはいけない
ということです。「私、趣味でプログラム学び始めただけだから一緒に開発してくれる友達なんていない!」と思う方、大丈夫です。知り合いを巻き込む必要は1mmもありません。
C/C++のビルドプロセスについて
大規模アプリを開発する上でビルドプロセスを理解することは必要不可欠ですのでその説明をします。
まず**ビルド(build)**とはヘッダ(header)・ソースコードから最終的に実行ファイルを作り出す行為を指します。この実行ファイルとはWindowsでいえば.exe
の拡張子がついているやつです。上記のHelloWorldの例だとa.out
という名前のファイルがまさに実行ファイルになります。
具体的な流れは次の図をご覧ください。
-
プリプロセス(preprocess)
プリプロセスとは、#
で始まる命令を実行することです。#include
や#define
、#if
, ...等がこの処理の対象になります。また、この#
で始まる命令のことをプリプロセッサ指令と言います。そのまんまですね。このプリプロセスにより作られるファイルのことを一時ファイルといいます。次の処理のために一時的に作ったファイルという意味です。 -
コンパイル (compile)
テンポラリーファイルをアセンブリ言語で書かれたファイルに変換する行為です。構文エラー等C/C++言語として間違ってる書き方をしてしまった場合この段階でコンパイルを中断し、中断した理由を教えてくれます。ただC/C++言語はこの理由の伝え方がやたら下手なのか意味が分からないことが多いです。がんばりましょう。 -
アセンブル (assemble)
アセンブリ言語が書かれたファイルをオブジェクトコードに変換します。アセンブリ言語とは人間が理解できる機械語の一歩手前みたいな言語で、それを機械が理解できる01011011...ってやつに変換する行為がアセンブルです。このアセンブルによって生成される01011011...がいっぱい書かれたファイルをオブジェクトコードと言います。 -
リンク (link)
オブジェクトコードは自分で作ったソースコードから生成されたもの以外に、既に用意されているものが存在します。例えばprintfの動作を行うオブジェクトコードです。このような実際動かすうえで必要なオブジェクトコードをすべてくっつけて実行ファイルを生成する行為をリンク(link)といいます。
以上が基本的なビルドの流れとなります。
このビルドという行為は「何かを作り上げる」という意味を主体に持っていることに注意してください。
例えば、jsonファイルに書かれたデータを読み取っていきなり実行ファイルを生成するプログラムがあったとして、この過程を全部すっ飛ばしたような行為もまたビルドと言えるでしょう。
他人のソースを取り込む方法
他人が作ったソースというのは次のような形で公開されていることが多いです。
あと、「他人が作ったもの」をソフトウェア業界では一般的に「3rd party製」と呼ぶのでこれ以降は3rd party製と言い方を変えて記述します。
パターン1 完全なオープンソース
基本要素
- ソースコード (*.cpp, *.cc)
- ヘッダファイル (*.h, *.hpp)
この場合、まず3rd party製のソースコードをビルドし、ライブラリというファイルを作成します。このライブラリには2種類あります。
-
静的(static)リンクライブラリ
最終的に実行ファイルにはこのライブラリの処理が取り込まれるため、実行ファイルを静的リンクライブラリを持っていないPCに移動してもPCでも実行ファイルを実行できます。 -
動的(shared|dynamic)リンクライブラリ
動的リンクライブラリは実行ファイルが実際に動くときに初めて取り込まれます。なので**実行ファイルを動的リンクライブラリを持っていないPCに移動した場合、実行ファイルは動きません。**ただし、動的リンクライブラリを使った実行ファイルの容量は小さくなるという利点があります。例えば実行ファイルA, B, Cが、ある共通のライブラリXを使う場合、静的リンクより動的リンクのほうがディスク容量を圧迫せずに済むというわけです。あるOS(例えばWindows)にどのアプリケーションも行うような処理がある場合、その処理は動的リンクライブラリとしてOSが公開している場合が多いです。
静的リンクと動的リンクはこのような違いがあるので用途に合わせて変えてください。
ライブラリを作ったら次の手順でビルドを行います。
- ライブラリが持つヘッダファイルを含めてプリプロセスを行う
- 最後のリンクの段階で静的リンクライブラリを繋げる
- 動的リンクライブラリは実行時に取り込まれるので、動的リンクライブラリが取り込まれるディレクトリに配置する。(実行ファイルと同じ場所、環境変数
PATH
の示す場所等)
パターン2 ビルド済みのライブラリが公開されている
基本要素
- ヘッダファイル (*.h, *.hpp)
- 静的(static)リンクライブラリ (*.so)
- 動的(shared|dynamic)リンクライブラリ (*.so, *.dll(Windows))
このパターンはライブラリを既にビルドし終わっているので、オープンソースの場合におけるライブラリ作成後と同じ手順でビルドすればOKです。
以上がビルドの概要になります。
ビルドから学ぶソフトウェアの開発方法
1. 自分がやりたいことを含む3rd party製ライブラリを探す
自分一人で全て作っては寿命のうちにものが完成しないということを思い出し、3rd party製ライブラリを探しましょう。
私は一応ロボットのAI関係の人間なので次のライブラリをよく使います。
ライブラリ名 | 用途 |
---|---|
Boost | C++の色々便利なライブラリ。これを知らずしてC++を語ってはいけないレベル |
OpenCV | 画像処理・認識・取り込み・表示 etc |
Eigen3 | 行列・ベクトル計算 |
Qt | GUIアプリケーションの作成 |
PCL | 点群から3次元空間の色々な情報を得る |
ライブラリ以外にもフレームワークという、ライブラリ含むプログラムをフルセットでまとめ上げて一つの開発環境として提供する素晴らしいものがあります。例えば3Dゲームを作ることに特化しているUnity3D, Unreal Engineがその例です。フレームワークはライブラリと違いそのメンテナンス費用がシャレにならないので商用では有料というケースが多いです。
お金が掛かるなら無料のものだけで作ればいいやというそこのあなた、1回GPUを使って3Dの物理計算を行うプログラムをゲームエンジンを使わずに作ってみるといいですよ。
2. 各3rd party製のライブラリが提供する処理を繋げてやりたいことを実現するプログラムを書く
大体3rd party製のライブラリを持ってくるだけでは肝心のやりたいことはできません。なのでそいつらをうまくつなげるプログラムを作る必要があります。
3. ビルド
C/C++の例だけ示していますが、言語によってこれが得意・あれは不得意といった特徴があります。(例えば、pythonは機械学習のフレームワークが多いので機械学習が得意、javascriptはnode.jsを使えばサーバー関連の処理が簡単に書ける等)
一つの言語に囚われず、いろいろな言語を見てどのような処理が得意といったことを把握することが大事です。
おすすめの勉強方法
- 自分が何を作りたいのか明確にし、細分化する
- 色々なライブラリを知る
- 小さな規模からコツコツ実装し、広げていく
- OSSコミュニティーに参加する(難易度高)
- OSSのプログラムを読む
色々あります。勉強する内容は果てしなく広いですが頑張りましょう。