【初心者C++er Advent Calendar 2017】#include <iostream> は何をやっているのか【1日目】
初心者C++er Advent Calendar 2017 1日目の記事になります。
別の Advent Calendar の記事を書いていたら遅れてしまって申し訳ない。
まだ、Advent Calendar の参加者に飽きがあるので気になっている人はどんどん参加してもええんやで…。
さて、初心者ネタって言うことで C++ (や C言語)を学び始めると『おまじない』とよく言われる #include
についてちょっと解説してみようかと思います。
コンパイルの流れ
#include
を理解するにあたってコンパイルがどのように処理されているのかを知る必要があります。
コンパイル時の流れをざっくりまとめると以下のような感じになります。
#include
はここでいう『プリプロセス』という処理に該当します。
プリプロセス処理
プリプロセス処理とはその名の通り『コンパイルの前に行われる』処理になります。
#include
以外にも #define
や #if
、#error
など #
から始まる命令がプリプロセス処理に該当します。
また、このような命令の事をプリプロセッサと呼びます。
#include
は何をやっているのか
#include
はプリプロセッサというコンパイル前に処理される命令というのはわかりました。
では、#include
は何をするのでしょうか。
#include
は『指定されたファイルをその場所に展開する(読み込む)』というような命令になります。
例えば、以下のようなヘッダーファイル(test.h
)があった時に
// test.h struct X{ int value; int value2; };
以下のように #include "test.h"
してみます。
// main.cpp #include "test.h" int main(){ X x{}; return 0; }
では、実際にどのようにプリプロセスが行われるのか見てみましょう。
clang や gcc では -E
オプションで『プリプロセスの結果を出力する』ことができるのでこれを利用して試してみましょう。
# -P は『'#line'指示子を生成しないようにする』オプション $ clang++ -E -P main.cpp struct X{ int value; int value2; }; int main(){ X x{}; return 0; }
このように #include "test.h"
で test.h
を読み込んでいることがわかると思います。
ちなみに今回は #include
を確認するために -E
オプションを使いましたが、#define
を使ったマクロなどを確認する場合にも -E
を利用することができます。
#include <hoge.h>
と #include "hoge.h"
の違い
さて #include
を行う場合に #include <iostream>
みたいに
<filename>
を使った書き方
と #include "test.h"
みたいに
"filename"
を使った書き方
の 2種類の書き方があります。
では、この違いはなんなのでしょうか。
この2つは『ファイルを読み込む場所の優先順位』が異なります。
こんな感じです。
なので <iostream>
とすれば標準ライブラリの iostream
ファイルを読み込み、"iostream"
とすれば(同じディレクトリに iostream
ファイルがあれば)同ディレクトリの iostream
ファイルを読み込みます。
まあ平たくいえば
- ライブラリのファイルは
<filename>
で読み込む - ローカルのファイルは
"filename"
で(相対パスで)読み込む
って感じで使い分ける事が一般的だと思います。
ただ、この辺り、処理系依存なのが言語仕様なのかがよくわからなかったので詳しく書いてくれる人を募集します。
Advent Calendar にはまだ飽きがありますぞ!
インクルードガードの必要性
さて、#include
が何をやっているのかわかってきたと思います。
では次にヘッダーファイルを作る時によく耳にする『インクルードガード』について説明してみたいと思います。
例えば、次のように同じファイルを #include
しているコードがあるとします。
#include "test.h" #include "test.h" int main(){ X x{}; return 0; }
上記のコードだとちょっと極端ですが、複数のヘッダーファイルから同じヘッダーファイルを読み込んでいることはよくあると思います。
#include
では同じファイルが何回も読み込まれるのでこの場合、
struct X{ int value; int value2; }; struct X{ int value; int value2; }; int main(){ X x{}; return 0; }
のように #include "test.h"
が2回読み込まれます。
しかし、上記のコードのように同名のクラスを複数定義することは C++ では不正なのでコンパイルエラーとなってしまいます。
こういう時に利用するのが『インクルードガード』という手法になります。
インクルードガードを追加した test.h
は以下のような感じになります。
// test.h #ifndef TEST_H #define TEST_H struct X{ int value; int value2; }; #endif /* TEST */
このように
- 1回目の読み込み時に
#define TEST_H
でマクロで定義する - 2回目以降の読み込みでは
#ifndef TEST_H
を用いて処理を読み飛ばす
とすることで2回目以降の定義を防止する事ができます。
余談ですが同等の機能を持つ #pragma once
というプリプロセッサが使える処理系もあります。
ちなみに TEST_H
みたいなマクロ名をよく __TEST_H__
と書いているコードがありますが、__
を含めた名前は規約違反なので使わないでください。
ヘッダーファイル以外を読み込む
C言語から C++ に入ると気になると思うんですが #include <iostream>
は #include <stdio.h>
のように拡張子がありません。
これは C++ では『拡張子を省略して書ける』わけではなくて単に iostream
ファイルに『拡張子がないだけ』です。
逆にいえば『テキストファイルであればなんでも』読み込むことが出来ると言うことになります。
なので、次のように『ただのテキストファイルにデータを記述してく』ことで『コンパイル時にそのデータを読み込むこと』ができます。
data.txt
1, 2, 3, 4, 5, 6, 7, 8, 9,
main.cpp
int main(){ auto data = { #include "data.txt" }; return 0; }
結果
$ clang++ -E -P main.cpp int main(){ auto data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, }; return 0; }
出力結果は整形されていないのでちょっと不格好ですが、こんな感じにソースコードに直接データを埋め込むことができます。
これによりソースコード外で設定などを記述する事が可能です。
まとめ
#include <iostream>
は標準ライブラリのiostream
というファイルを読み込んでいる- ヘッダーファイルを作る場合はインクルードガードを付ける
#include
はテキストファイルであればなんでも読み込める
と、言うことで簡単に #include
についてまとめてみました。
『#include
はおまじない』と言われていますがやっていることは単純にファイルを読み込んでその場に展開しているだけなのでそんなに難しくはないと思います。
何か質問等があればコメントや Twitter までおねがいします。
斧を投げたい人はその旨を書いた 初心者C++er Advent Calendar 2017 への登録をおねがいします。