CプリプロセッサーはC++がC言語から受け継いだ機能だ。CプリプロセッサーはソースコードをC++としてパースする前に、テキストをトークン単位で変形する処理のことだ。この処理はソースファイルをC++としてパースする前処理として行われる。CプリプロセッサーはC++ではなく別言語として認識すべきで、そもそもプログラミング言語ではなくマクロ言語だ。
C++ではCプリプロセッサーが広く使われており、今後もしばらくは使われるだろう。読者がC++で書かれた既存のコードを読むとき、Cプリプロセッサーは避けて通れない。Cプリプロセッサーはいずれ廃止したい機能ではあるが、C++はいまだに廃止できていない。
Cプリプロセッサーはプリプロセッシングディレクティブ(preprocessing directive)を認識し、トークン列を処理する。ディレクティブはソースファイルの文頭に文字#
から始まり、改行文字で終わる。#
とディレクティブの間に空白文字を入れてもよい。
#define NOSPACE
# define SPACE
#include
は指定したファイルの内容をその場に挿入する。本質的にはコピペだ。C++では#include
はライブラリを利用するのに使われる。
#include
は以下のいずれかの文法を持つ。
#include <ヘッダーファイルパス> 改行文字
#include "ヘッダーファイルパス" 改行文字
#include
は指定したファイルパスのファイルの内容をその場所に挿入する。このファイルをヘッダーファイルという。<>
によるファイルパスは、標準ライブラリやシステムのヘッダーファイルを格納したディレクトリーからヘッダーファイルを探す。""
によるファイルパスは、システム以外のディレクトリーからもヘッダーファイルを探す。例えばカレントディレクトリーなどだ。
例えば、以下のようなヘッダーファイルfoo.h
があり、
// foo.h
foo foo foo
以下のようなソースファイルbar.cpp
がある場合、
// bar.cpp
#include "foo.h"
// end bar.cpp
bar.cpp
をCプリプロセッサーにかけると、以下のようなソースファイルが出力される。
// bar.cpp
// foo.h
foo foo foo
// end bar.h
このソースファイルはC++のソースファイルとしてはエラーとなるが、Cプリプロセッサーは単純にトークン列で分割したテキストファイルとしてソースファイルを処理するため、Cプリプロセッサーとしてはエラーにはならない。
冒頭で述べたように、#include
の本質はコンパイラーによるコピペである。あるテキストファイルの内容をその場に挿入するコピペ機能を提供する。
#include
は、ほかの言語でモジュール、importなどと呼ばれている機能を簡易的に提供する。C++の標準ライブラリを使うには、<iostream>
や<string>
や<vector>
のようなヘッダーファイルを#include
する必要がある。
// iostreamライブラリを使う
#include <iostream>
// stringライブラリを使う
#include <string>
int main()
{
// <string>のライブラリ
std::string s("hello") ;
// iostreamのライブラリ
std::cout << s ;
}
すでに述べたように#include
はファイルの内容をその場に挿入するだけであり、ほかの言語にあるモジュールのための高級な機能ではない。本書を執筆時点で規格策定中のC++20では、より高級なモジュール機能を追加する予定がある。
同じヘッダーファイルを複数回#include
すると、当然複数回挿入される。
以下のようなval.h
を、
// val.h
inline int val ;
以下のように複数回#include
すると、
#include "val.h"
#include "val.h"
以下のように置換される。
// val.h
inline int val ;
// val.h
inline int val ;
これはval
の定義が重複しているためエラーとなる。
しかし、ヘッダーファイルを一度しか#include
しないようにするのは困難だ。なぜならば、ヘッダーファイルはほかのヘッダーファイルから間接的に#include
されることもあるからだ。
// lib_f.h
#include "val.h"
int f() ;
// lib_g.h
#include "val.h"
int g() ;
// main.cpp
#include "lib_f.h"
#include "lib_g.h"
int main()
{
int result = f() + g() ;
}
このmain.cpp
をCプリプロセッサーにかけると以下のように置換される。
// main.cpp
// lib_f.h
// val.h
inline int val ;
int f() ;
// lib_g.h
// val.h
inline int val ;
int g() ;
int main()
{
int result = f() + g() ;
}
これはval
の定義が重複しているためエラーとなる。
この問題に対処するためには、複数回#include
されると困るヘッダーファイルでは、インクルードガード(include guard)と呼ばれている方法を使う。
// val.h
#ifndef INCLUDE_GUARD_HEADER_VAL_H
#define INCLUDE_GUARD_HEADER_VAL_H
inline int val ;
#endif
このように記述したval.h
を複数回#include
しても、最初のifndef
のみがコンパイル対象になるため、問題は起こらない。
インクルードガードは以下の様式を持つ。
#ifndef 十分にユニークなマクロ名
#define 十分にユニークなマクロ名
// 重複してコンパイルされたくないコードをここに書く
#endif
十分にユニークなマクロ名
は全ソースファイル中で衝突しないそのヘッダーに固有のマクロ名を使う。慣習的に推奨される方法としてはすべて大文字を使い、十分に長いマクロ名にすることだ。
#define
はマクロ置換を行う。マクロにはオブジェクト風マクロ(object-like macro)と関数風マクロ(function-like macro)がある。風というのは、マクロはオブジェクトでも関数でもないからだ。ただ、文法上オブジェクトや関数の似ているだけで、実態はトークン列の愚直な置換だ。
オブジェクト風マクロの文法は以下のとおり。
#define マクロ名 置換リスト 改行文字
#define
以降の行では、マクロ名が置換リストに置き換わる。
#define ONE 1
#define ONE_PLUS_ONE ONE + ONE
#define GNU GNU's is NOT UNIX
ONE
ONE_PLUS_ONE
これをプリプロセスすると以下のソースコードになる。
1
1 + 1
マクロ名ONE
は1
に置換される。
マクロ名ONE_PLUS_ONE
はONE + ONE
に置換される。置換された結果に別のマクロ名があれば、そのマクロ名も置換される。
あるマクロ名を置換した結果、そのマクロ名が現れても再帰的に置換されることはない。
#define GNU GNU's NOT UNIX!
GNU
これは以下のように置換される。
GNU's NOT UNIX!
マクロ名GNU
を展開するとトークン`GNU'が現れるが、これは置換されたマクロ名と同じなので、再帰的に置換されることはない。
関数風マクロの文法は以下のとおり。
#define マクロ名( 識別子リスト ) 置換リスト 改行文字
関数風マクロはあたかも関数のように記述できる。関数風マクロに実引数として渡したトークン列は、置換リスト内で仮引数としての識別子で参照できる。
#define NO_ARGUMENT() No argument
#define ONE_ARGUMENT( ARG ) begin ARG end
#define MAKE_IT_DOUBLE( ARG ) ONE_ARGUMENT( ARG ARG )
NO_ARGUMENT()
ONE_ARGUMENT( foo bar )
MAKE_IT_DOUBLE( foo bar )
これは以下のように置換される。
No argument
begin foo bar end
begin foo bar foo bar end
複数の引数を取るマクロへの実引数は、カンマで区切られたトークン列を渡す。
#define TWO( A, B ) A B
#define THREE( A, B, C ) C B A
TWO( 1 2, 3 4 )
THREE( 1, 2, 3 )
これは以下のように置換される。
1 2 3 4
3 2 1
ただし、括弧で囲まれたトークン列の中にあるカンマは、マクロの実引数の区切りとはみなされない。
#define MACRO( A ) A
MACRO( (a,b) )
これは以下のように置換される。
(a,b)
#define
の識別子リストを...
だけにしたマクロは、可変長引数マクロになる。このときマクロの実引数のトークン列は、置換リストの中で__VA_ARGS__
として参照できる。
#define MACRO(...) __VA_ARGS__
MACRO( You can write , and ,, or even ,,,, )
これは以下のように置換される。
You can write , and ,, or even ,,,,
カンマも含めてすべてのトークン列がそのまま__VA_ARGS__
で参照できる。
可変長引数マクロの識別子リストに仮引数と...
を書いたマクロの置換リストでは、仮引数の数だけの実引数は仮引数で参照され、残りが__VA_ARGS__
で参照される。
#define MACRO( X, Y, Z, ... ) X Y Z and __VA_ARGS__
MACRO( 1,2,3,4,5,6 )
これは以下のように置換される
1 2 3 and 4,5,6
X
, Y
, Z
にそれぞれ1
, 2
, 3
が入り、__VA_ARGS__
には4
, 5
, 6
が入る。
__VA_OPT__
は可変長引数マクロで__VA_ARGS__
にトークン列が渡されたかどうかで置換結果を変えることができる。
__VA_OPT__
は可変引数マクロの置換リストでのみ使える。__VA_OPT__(content)
は__VA_ARGS__
にトークンがない場合はトークンなしに置換され、トークンがある場合はトークン列content
に置換される。
#define MACRO( X, ... ) f( X __VA_OPT__(,) __VA_ARGS__ )
MACRO(1)
MACRO(1,2)
これは以下のように置換される。
f( 1 )
f( 1, 2 )
MACRO(1)
はX
が1
になり、__VA_ARGS__
にはトークンがないので、__VA_OPT__(,)
は空に置換される。結果としてf(1)
となる。
MACRO(1,2)
は、X
が1
になり、__VA_ARGS__
にはトークン2
が入るので、__VA_OPT__(,)
は,
に置換される。結果としてf(1,2)
となる。
__VA_OPT__
は__VA_ARGS__
に実引数となるトークン列がなければ空に置換されるので、このようにトークン列の有無によってカンマなどの文法上必須のトークン列の有無を切り替えたい場合に使うことができる。
#
はマクロ実引数を文字列リテラルにする。
#
は関数風マクロの置換リストの中のみで使うことができる。#
は関数風マクロの仮引数の識別子の直前に書くことができる。#
が直前に書かれた識別子は、マクロ実引数のトークン列の文字列リテラルになる。
#define STRING( X ) # X
STRING( hello )
STRING( hello world )
これは以下のように置換される。
"hello"
"hello world"
また、可変長マクロと組み合わせた場合、
#define STRING( ... ) # __VA_ARGS__
STRING()
STRING( hello,world )
以下のように置換される。
""
"hello,world"
##
はマクロ実引数の結合を行う。
##
は関数風マクロの置換リストの中にしか書けない。##
は両端にマクロの仮引数の識別子を書かなければならない。##
は両端の識別子の参照するマクロ実引数のトークン列を結合した置換を行う。
#define CONCAT( A, B ) A ## B
CONCAT( foo, bar )
CONCAT( aaa bbb, ccc ddd)
これは以下のように置換される。
foobar
aaa bbbccc ddd
結合した結果のトークンはさらにマクロ置換の対象となる。
#define CONCAT( A, B ) A ## B
#define FOOBAR hello
CONCAT( FOO, BAR )
これは以下のように置換される。
hello
CONCAT(FOO,BAR)
はFOOBAR
に置換され、FOOBAR
という名前のマクロ名があるためにさらにhello
に置換される。
#define
ディレクティブの置換リストは複数行に渡って書くことができない。これは文法上の制約によるものだ。#define
ディレクティブは改行文字で終端される。
しかし、関数やクラスを生成するような複雑なマクロは、複数行に分けて書きたい。
#define LIST_NAME2( PREFIX, TYPE ) PREFIX ## TYPE
#define LIST_NAME( TYPE ) LIST_NAME2( list_, TYPE )
#define DEFINE_LIST( TYPE ) struct LIST_NAME(TYPE){TYPE value ;LIST_NAME(TYPE) * prev ;LIST_NAME(TYPE) * next ;} ;
DEFINE_LIST(int)
DEFINE_LIST(double)
この場合、行末にバックスラッシュに続けて改行を書くと、バックスラッシュと改行がプリプロセッサーによって削除される。
上の例は以下のように、プリプロセッサーとしては比較的わかりやすく書くことができる。
#define LIST_NAME2( PREFIX, TYPE ) PREFIX ## TYPE
#define LIST_NAME( TYPE ) LIST_NAME2( list_, TYPE )
#define DEFINE_LIST( TYPE )\
struct LIST_NAME(TYPE)\
{\
TYPE value ;\
LIST_NAME(TYPE) * prev ;\
LIST_NAME(TYPE) * next ;\
} ;
DEFINE_LIST(int)
DEFINE_LIST(double)
C++ではテンプレートがあるために、このようなマクロを書く必要はない。
#undef
はそれ以前に定義されたマクロを削除する。
#define FOO BAR
FOO
#undef FOO
FOO
これは以下のように置換される。
BAR
FOO
#if
, #elif
, #else
, #endif
, #ifdef
, #ifndef
は条件付きのソースファイルの選択(conditional inclusion)を行う。これは条件付きコンパイルに近い機能を提供する。
プリプロセッサーで使える条件式は、C++の条件式と比べてだいぶ制限がある。基本的には整数定数式で、true
, false
が使えるほか、123
, 1+1
, 1 == 1
, 1 < 1
のような式も使える。ただし、識別子はすべてマクロ名として置換できるものは置換され、置換できない識別子は、true
, false
以外はキーワードも含めてすべて0
に置換される。
したがって、プリプロセッサーで以下のように書くと、
#if UNDEFINED
#endif
以下のように書いたものと同じになる。
#if 0
#endif
プリプロセッサーであるので、C++としてのconstexpr
変数やconstexpr
関数も使えない。
constexpr int x = 1 ;
#if x
hello
#endif
これは以下のように置換される。
constexpr int x = 1 ;
プリプロセッサーはC++の文法と意味を理解しない。単にトークン列として処理する。
以下の例はエラーになる。
constexpr int f() { return 1 ; }
#if f()
#endif
なぜならば、0()
は整数定数式として合法なコードではないからだ。何度も言うように、プリプロセッサーはC++の文法と意味を理解しない。
プリプロセッサーの定数式では、特殊なマクロ風の式を使うことができる。defined
と__has_include
だ。
defined
は以下の文法を持つ。
defined 識別子
defined ( 識別子 )
defined
は識別子がそれ以前の行で#define
でマクロとして定義されていて#undef
で取り消されていない場合1
になり、それ以外の場合0
になる。
// #if 0
#if defined MACRO
#endif
#define MACRO
// #if 1
#if defined MACRO
#endif
#undef MACRO
// #if 0
#if defined MACRO
#endif
__has_include
は以下の文法を持つ。
__has_include ( < ヘッダーファイル名 > )
__has_include ( " ヘッダーファイル名 " )
__has_include ( 文字列リテラル )
__has_include ( < マクロ > )
1番目と2番目は、指定されたヘッダーファイル名がシステムに存在する場合1
に、そうでない場合0
になる。
// <filesystem>の存在を確認してから#includeする
#if __has_include(<filesystem>)
# include <filesystem>
#endif
// "mylibrary.h"の存在を確認してから#includeする
#if __has_include("mylibrary.h")
# include "mylibrary.h"
#endif
3番目と4番目は、1番目と2番目が適用できない場合に初めて考慮される。その場合、まず通常通りにプリプロセッサーのマクロ置換が行われる。
#define STDIO "stdio.h"
#if __has_include( STDIO )
#endif
#define STDLIB stdlib.h
#if __has_include( <STDLIB> )
#endif
#if
ディレクティブは以下の文法を持つ。
#if 定数式 改行文字
#endif
もし定数式がゼロの場合、#if
と#endif
で囲まれたトークン列は処理されない。定数式が非ゼロの場合、処理される。
#if 0
This line will be skipped.
#endif
#if 1
This line will be processed.
#endif
これをプリプロセスすると以下のようになる。
This line will be processed.
#if 0
は処理されないので、#endif
までのトークン列は消える。
#elif
ディレクティブは、C++でいうelse if
に相当する。
#elif 定数式 改行文字
#elif
ディレクティブは#if
ディレクティブと#endif
ディレクティブの間に複数書くことができる。#elif
のある#if
が処理される場合、#if
から#elif
の間のトークン列が処理される、#if
が処理されない場合、#elif
が#if
と同じように定数式を評価して処理されるかどうかが判断される。#elif
が処理される場合、処理されるトークン列は次の#elif
もしくは#endif
までの間のトークン列になる。
以下の例は、すべてYES
のトークンがある行のみ処理される。
#if 1
YES
#elif 1
NO
#endif
#if 0
NO
#elif 1
YES
#endif
#if 0
NO
#elif 1
YES
#elif 1
NO
#endif
#if 0
NO
#elif 0
NO
#elif 1
YES
#endif
プリプロセスした結果は以下のとおり。
YES
YES
YES
YES
#else
ディレクティブはC++でいうelse
に相当する。
#else
ディレクティブは#if
ディレクティブと#endif
ディレクティブの間に書くことができる。もし#if
と#elif
ディレクティブが処理されない場合で#else
ディレクティブがある場合、#else
から#endif
までのトークン列が処理される。
以下の例は、YES
のトークンがある行のみ処理される。
#if 1
YES
#else
NO
#endif
#if 0
NO
#else
YES
#endif
#if 0
NO
#elif 1
YES
#else
NO
#endif
#ifdef 識別子
#ifndef 識別子
は、それぞれ以下と同じ意味になる。
#if defined 識別子
#if !defined 識別子
例、
#ifdef MACRO
#endif
// 上と同じ
#if defined MACRO
#endif
#ifndef MACRO
#endif
// 上と同じ
#if !defined MACRO
#endif
#line
ディレクティブはディレクティブの次の行の行番号と、ソースファイル名を変更する。これは人間が使うのではなく、ツールによって生成されることを想定した機能だ。
以下の文法の#line
ディレクティブは、#line
ディレクティブの次の行の行番号をあたかも数値で指定した行番号であるかのように振る舞わせる。
#line 数値 改行文字
数値として0もしくは2147483647より大きい数を指定した場合の挙動は未定義となる。
以下の例はコンパイルエラーになるが、コンパイルエラーメッセージはあたかも102行目に問題があるかのように表示される。
// 1行目
// 2行目
#line 100 // 3行目
// 100行目
// 101行目
ill-formed line // 102行目
以下の例は999
を出力するコードだ。
#include <iostream>
int main()
{
#line 999
std::cout << __LINE__ ;
}
以下の文法の#line
ディレクティブは、次の行の行番号を数値にした上で、ソースファイル名をソースファイル名にする。
#line 数値 "ソースファイル名" 改行文字
例、
#line 42 "answer.cpp"
以下の文法の#line
ディレクティブは、プリプロセッサートークン列をプリプロセスし、上の2つの文法のいずれかに合致させる。
#line プリプロセッサートークン列 改行文字
例、
#define LINE_NUMBER 123
#line LINE_NUMBER
#error
ディレクティブはコンパイルエラーを引き起こす。
#error 改行文字
#error トークン列 改行文字
#error
によるコンパイラーのエラーメッセージには#error
のトークン列を含む。
#error
の利用例としては、#if
と組み合わせるものがある。以下の例はCHAR_BIT
が8でなければコンパイルエラーになるソースファイルだ。
#include <climits>
#if CHAR_BIT != 8
#error CHAR_BIT != 8 implementation is not supported.
#endif
#if
が処理されなければ、その中にある#error
も処理されないので、コンパイルエラーにはならない。
#pragma
ディレクティブは実装依存の処理を行う。#pragma
はコンパイラー独自の拡張機能を追加する文法として使われている。
文法は以下のとおり。
#pragma プリプロセッサートークン列 改行文字
C++では属性が追加されたために、#pragma
を使う必要はほとんどなくなっている。
Null
ディレクティブとは何もしないプリプロセッサーディレクティブだ。
# 改行文字
つまり、単に#
とだけ書いた行はエラーにはならない。
いくつかのマクロ名がプリプロセッサーによってあらかじめ定義されている。
マクロ名 値 意味
__cplusplus
201703L
C++17時点での値
将来の規格で増やされる
__DATE__
"Mmm dd yyyy"
ソースファイルがプリプロセスされた日付
Mmm
は月、dd
は日、yyyy
は年
月の文字列はasctime
が生成するものと同じ
日が1桁の場合、dd
の最初の文字は空白文字
__FILE__
文字列リテラル ソースファイルの名前の文字列リテラル
__LINE__
整数リテラル ソースファイルの現在の行番号
__STDC_HOSTED__
整数リテラル ホスト実装の場合1
フリースタンディング実装の場合0