最近、他の言語のことや、コンパイラ周りのことを勉強して、今更ながら分割コンパイルのことを理解してきたので、全く知識ゼロ向けの人に向けて、導入編を書いてみたいと思います。
自分が当時わからなかったこととか、あるいはこんな記事があればいいのにな、ということを想定して書いていますが、わかりにくかったり、間違ったことを書いていれば教えて頂けるとありがたいです。
訂正
「前提」のところで紹介しているコンパイラと実際に使用しているコンパイラが違ったので、直しました。
ヘッダをインクルードする場所について別の書き方もあるようで、その他誤解を与える書き方などを修正しました。
#前提
前提としてclang++が入っていることを想定しています。
ざっくり言えば、よくサンプルコードとして紹介されるHello worldが動く状態です。
ちなみに、whichコマンドを使って調べると、自分のパソコンにはclang++が入っていました。
$ which -a clang++
// 二つありますよ、という意味で2行出てくる。
/Users/hiroshi/.pyenv/shims/clang++ //単にclang++ で実行するとこちらのコンパイラが動く。
/usr/bin/clang++ // これがXcode付属のコンパイラ
バージョンを確認すると、
$/usr/bin/clang++ -v
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
となっているが、ここはよくわからないので、そんなもんかということで先へ進むことにする。最新バージョンはclang10のようだけど。。。?
#基本
まずは、なんのひねりもない、一番基本から。
Hello worldを表示するプログラムをコンパイルします。
コンパイルするコードは以下のコード。
#include<iostream>
using namespace std;
int main(){
cout << "Hello world \n" ;
return 0;
}
hello.cppがあるフォルダと同じフォルダに移動し、以下のコマンドでコンパイルする。
$ /usr/bin/clang++ -std=c++17 hello.cpp
-std=c++17 の部分はコマンドのオプションです。
「-std=なになに」でc++のどのバージョンとしてコンパイルできるかを指定できます。
このあたりの話は今回は省略します。また機会があれば詳しく書くかもしれません。
同じフォルダにa.outという実行ファイルができているので、それを実行します。
$ ./a.out // くどい説明かもしれないけど、「./」で「現在のディレクトリの中の」の意味。現在いるディレクトリの中のa.outというファイルを実行している。
Hello world
#分割コンパイル(ダメな例)
複数のソースコードをまとめてコンパイルすることもできます。
で、成功するやりかたの前に、誰しも一度はやってしまうであろう(そんなことない?)ダメなパターンを紹介します。
まず、以下のような2つのソースコードを作ります。
#include<iostream>
using namespace std;
int main(){
cout << "Hello world \n" ;
return 0;
}
#include<iostream>
using namespace std;
int main(){
cout << "Good morning! \n" ;
return 0;
}
これを、
$/usr/bin/clang++ -std=c++17 hello.cpp morning.cpp
と実行すると、
duplicate symbol _main in:
/var/folders/3j/6lblgwg16hsf8kqx9wl_5r440000gn/T/hello-eafe6a.o
/var/folders/3j/6lblgwg16hsf8kqx9wl_5r440000gn/T/morning-cca263.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
というエラーが掃き出される。
いくつかのファイルをまとめてコンパイルする場合、その中にmain関数は一個にしなければならないが、hello.cppのほうも、morning.cppのほうもmain関数になってしまっているのでエラーになっている。
#分割コンパイル(うごく方法)
##その1(ヘッダをインクルード)
ヘッダファイルを介してインクルードする。
同じディレクトリの中に、以下の3つのソースコードを用意する。
ディレクトリ構造も、書くまでもないが一応書いておく。
- greeting.cpp
- morning.cpp
- morning.hpp
#include<iostream>
#include "morning.hpp"
using namespace std;
int main(){
Morning();
return 0;
}
#include<iostream>
#include "morning.hpp"
using namespace std;
void Morning(){
cout << "Good morning! \n" ;
}
void Morning();
これで、
$ /usr/bin/clang++ -std=c++17 greeting.cpp morning.cpp
と実行する。そして、./a.outを実行すると、Good morning! と表示される。
その2(.cppファイルを直接インクルード)
直接.cppのファイルをインクルードすることもできる。
今度は、以下2つのファイルを用意する。
- greeting.cpp
- morning.cpp
#include<iostream>
#include "morning.cpp"
using namespace std;
int main(){
Morning();
return 0;
}
#include<iostream>
using namespace std;
void Morning(){
cout << "Good morning! \n" ;
}
そして、コンパイルする際には、以下のようにgreeting.cppだけをコンパイルする。
$/usr/bin/clang++ -std=c++17 greeting.cpp
これでも、またa.outを実行すれば、Good morning! と表示されるはず。
その3(複数ファイルを同時にコンパイル)
2つ以上のファイルを一度にコンパイルもできる。
再びヘッダファイルからインクルードする。
今度は以下のようなファイルを用意し同じディレクトリに置く。
ディレクトリ構造を書けば、
- greeting.cpp
- morning.cpp
- afternoon.cpp
- evening.cpp
- morning.hpp
- afternoon.hpp
- evening.hpp
#include<iostream>
#include "morning.hpp"
#include "afternoon.hpp"
#include "evening.hpp"
using namespace std;
int main(){
Morning();
Afternoon();
Evening();
return 0;
}
#include<iostream>
#include "morning.hpp"
using namespace std;
void Morning(){
cout << "Good morning! \n" ;
}
#include<iostream>
#include "afternoon.hpp"
using namespace std;
void Afternoon(){
cout << "Good afternoon! \n" ;
}
#include<iostream>
#include "evening.hpp"
using namespace std;
void Evening(){
cout << "Good evening! \n" ;
}
void Morning();
void Afternoon();
void Evening();
それで、以下のようにコマンドを実行する。
$ /usr/bin/clang++ -std=c++17 greeting.cpp morning.cpp afternoon.cpp evening.cpp
$ ./a.out
そうすると以下のようになるはずです。
Good morning!
Good afternoon!
Good evening!
その4(ヘッダファイルを一つの場所にまとめる)
ヘッダファイルだけを一つのディレクトリにまとめてしまうこともできる。
先ほどの、greeting.cpp〜evening.hppをまた使うことにする。
ソースコードはさすがにくどいので省略。
ディレクトリ構造は以下のようになっているとする。
- greeting.cpp
- morning.cpp
- afternoon.cpp
- evening.cpp
- my_include このディレクトリは新しくつくる。その中に以下の3つを入れる。
- morning.hpp
- afternoon.hpp
- evening.hpp
$ /usr/bin/clang++ -std=c++17 greeting.cpp morning.cpp afternoon.cpp evening.cpp -I ./my_include
$ ./a.out
そうすると、また同様に以下のように表示されるはずです。
Good morning!
Good afternoon!
Good evening!
長くなってきたので、今回はここまでにしておきます。
まだまだいろいろあるので、また次回以降にしようと思います。
参考にしたページや文献・書籍
CMakeの使い方
ハローワールド徹底解説
Clang10 documentation
C/C++のビルドの仕組みとライブラリ