【初心者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つは『ファイルを読み込む場所の優先順位』が異なります。

  • <filename> の場合

    • 標準(ライブラリ)のインクルードディレクトリを優先して読み込む
  • "filename" の場合

    • #include しているファイルのディレクトリを優先して読み込む

こんな感じです。
なので <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 への登録をおねがいします。