2019年8月12日月曜日

C++のコンパイルいろいろ(基礎から応用まで) --- その1

最近、他の言語のことや、コンパイラ周りのことを勉強して、今更ながら分割コンパイルのことを理解してきたので、全く知識ゼロ向けの人に向けて、導入編を書いてみたいと思います。

自分が当時わからなかったこととか、あるいはこんな記事があればいいのにな、ということを想定して書いていますが、わかりにくかったり、間違ったことを書いていれば教えて頂けるとありがたいです。

前提

前提としてclang++が入っていることを想定しています。

ざっくり言えば、よくサンプルコードとして紹介されるHello worldが動く状態です。

ちなみに、以前紹介したwhichコマンドを使って調べると、自分のパソコンにはclang++が入っていました。
$ which -a clang++
#二つありますよ、という意味で2行出てくる。
/Users/hiroshi/.pyenv/shims/clang++       #単にclang++ で実行するとこちらのコンパイラが動く。
/usr/bin/clang++    #これがXcode付属のコンパイラ
バージョンを確認すると、
/usr/bin/clang++
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のようだけど。。。?

参考 : Clang 10 documentation


基本

まずは、なんのひねりもない、一番基本から。

Hello worldを表示するプログラムをコンパイルします。

コンパイルするコードは以下のコード。
// ファイル名はhello.cppとする。
#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つのソースコードを作ります。
// 先ほどのhello.cpp
#include<iostream>

using namespace std;

int main(){

  cout << "Hello world \n" ;

  return 0;
}
// 2つめのファイルはmorning.cppとしておく。
#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
// 今度はgreeting.cppという名前にする。
#include<iostream>
#include "morning.hpp"
using namespace std;

int main(){

  Morning();

  return 0;
}

//これはmorning.cppという名前にする。
#include<iostream>

using namespace std;

void Morning(){

  cout << "Good morning! \n" ;

}
// これはmorning.hppとする。
void Morning();
これで、
$ /usr/bin/clang++ -std=c++17  greeting.cpp morning.cpp
と実行する。そして、./a.outを実行すると、Good morning! と表示される。

  その2

直接.cppのファイルをインクルードすることもできる。

今度は、以下2つのファイルを用意する。
  • greeting.cpp
  • morning.cpp
// このファイルはgreeting.cppとする。
#include<iostream>
#include "morning.cpp"
using namespace std;

int main(){

  Morning();

  return 0;
}
 //これはmorning.cppとする
#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
 // これはgreeting.cppという名前にしておく。
#include<iostream>

#include "morning.hpp"
#include "afternoon.hpp"
#include "evening.hpp"
using namespace std;

int main(){

  Morning();
  Afternoon();
  Evening();

  return 0;
}
 // これはmorning.cppという名前にしておく。
#include<iostream>

using namespace std;

void Morning(){

  cout << "Good morning! \n" ;

}
 //これはafternoon.cppという名前にしておく。
#include<iostream>

using namespace std;

void Afternoon(){

  cout << "Good afternoon! \n" ;

}
 //これはevening.cppという名前にしておく。
#include<iostream>

using namespace std;

void Evening(){

  cout << "Good evening! \n" ;

}

 //これはmorning.hppという名前にしておく。
void Morning();
 //これはafternoon.hppという名前にしておく。
void Afternoon();
 //これはevening.hppという名前にしておく。
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!


長くなってきたので、今回はここまでにしておきます。

まだまだいろいろあるので、また次回以降にしようと思います。

0 件のコメント:

コメントを投稿