プロプロセッサと定数まとめ

プリプロセッサー

お待たせしました。ようやくプリプロセッサーのお話です。
はい、#includeとかなにげに書いてきたものが何なのかわかります。
はじめに言うと、プリプロセッサのしごとをなるべくなくすべきです。なぜならばプリプロセッサには型の概念がないからです。
このことにより思わぬ副作用を招くことがあり、またその記法は特殊なものになってしまいます。

これだけ種類が有ります。解説するのは#includeと#defineにとどめます。他は
http://itref.fc2web.com/c/preprocessor.html
を参照してください。とてもわかり易いので。なお、#if/#else/#ifdef/#ifndefはこれまでもこれからもしれっと使います。

#include

.cとか.cppが読み込めないわけではないのですが、ほぼ100%ヘッダーファイルを読み込むのに使います。

#include "DxLib.h"
#include <stdio.h>

一般的なお話として、<>で囲むとコンパイラーの規定の場所とコンパイルオプションで指定した(gccなら-Iオプション)場所からヘッダーファイルを探します。
また、""で囲むと、それに加えて、#includeを書いたファイルと同じ場所も捜索対象になります。
ゆえに自分で作ったヘッダーは""で囲い、C言語標準ライブラリ―のヘッダーは<>で囲むのが普通です。ありきたり。

#define

#defineはコンパイル前にソースコードを置換するものです。で、「マクロ」と呼ばれます。Excelとかのマクロとはちと違うので注意です。

#define WINDOW_HEIGH 1024

こんな風に定数っぽいのを作ることもできますし

#define MAX(A, B) (A > B)? A : B

という表現もできます。

インクルードガード

ヘッダーファイルが複数読み込まれると、2重定義となりコンパイルエラーになります。この対策として

#ifndef FOO_H
#define FOO_H
//ヘッダーファイルの中身を書く
#endif // FOO_H

といったことをします。こういう書き方をインクルードガードといいます。
インクルードガードで問題になるのが名前空間の衝突です。その対策として、特定の接頭辞をつける、UUIDをつけるなどがありますが、素直にファイル名の英字を全て大文字に直し、それ以外をアンダーバー ('_')にするというのもあります。その際には、この条件で同じマクロ名になってしまうファイルは置かない、"_H"で終わるマクロを他に定義しないといったことを決めておきます。特に同じマクロ名になってしまうファイル名は、普通にインクルードするときにも紛らわしいので使うべきではありません。
アンダーバー1文字と英字大文字1字、アンダーバー2文字のどちらかで始まるものは処理系に予約されているので、ライブラリでもない限り使うべきではありません。

#define _FOO_H // だめ
#define __foo_h // これもだめ

定数

C言語における定数は3つあります。

実行時定数
プログラムの実行中に変数を読み取り専用にすることで、書き換えができなくなるようにするもの
eg.)const
コンパイル時定数
コンパイル時に値を決定できるので計算してしまい単なる数値などのリテラルにするもの
eg.)enum X { ver = 0}, constexpr, enum class
プリプロセス時定数
プリプロセス時に値を決定できるのでソースコードを置換してしまうもの
eg.)#define

ではそれぞれ見て行きましょう。

const

constは例えばこんなふうに書けるのでした。

const int x = 7;

ところで、int型のように小さい型ならいいのですが、この後出てくる構造体や大きな配列は大きいサイズなので、スタックにコピーされ、非効率です。
そこで

static const char foo[] = "foo";

のようにstaticをつけるといいです。static変数の生存期間はスコープに左右されず(だってスタックに積まれないもん)プログラムの開始から終了までとなります。
中にはconstをつけるならstatic constにするべきだ、という宗教も存在しますが、スタックにコピーされないものもあるのでおすすめしません。

一方で関数の外に書く場合は、グローバル変数(どこからでもアクセスできる変数)になるのを防ぐために必ずstaticをつけるべきです。
こうすることで有効範囲がそのファイルに限定されます。includeしても使えません。
これは関数においても同様です。

#define

これはプリプロセッサマクロで、例えばこんなふうに書けるのでした。

#define VAR 5

ところで#defineマクロにはいくつかの問題点があり、あまり使うことはありません。では問題点を見て行きましょう。

単なるソースコード置換であることに起因する問題

早速例を見ましょう。

#define BASE_SIZE 1 << 8
int main()
{
	auto tmp = BASE_SIZE + 4;
	return 0;
}

tmpの型はいうまでもなくintですね。で、tmpには何が入るでしょうか?260?いえいえ、4096です。なぜならばこれは以下のように置換されるからです。

auto tmp = 1 << 8 + 4;

つまり

auto tmp = 1 << 12;

となっています。まあ、この場合は

#define BASE_SIZE (1 << 8)

とすればいいんですけどね。

置換がプロプロセス時であることに起因する問題

#defineの値はコンパイルより前、プリプロセス時に置換されます。なのでコンパイラーには変数名がわからないのです。これによりコンパイルエラーなどで原因究明が困難になります。

スコープがないことに起因する問題

#defineとenum以外はすべてスコープの概念があります。
スコープの概念がないと何が問題かというと、#defineに関して言えば書いたファイルとそれをincludeしているファイルでその名前が消費されるので、変数名の衝突が起こしやすくなります。

参考サイト
定数の定義は,const intか,#defineか,それとも - わさっき
http://d.hatena.ne.jp/takehikom/20140807/1407420548
#defineの罠
http://www.geocities.co.jp/bleis_tift/cpp/baddefine.html
C言語のマクロの注意点
http://www.c-lang.org/detail/macro_caution.html