1. トップ
  2. HSP講座
  3. プリプロセッサ命令編
  4. >ここ [
    1. ←前
    2. |
    3. 次→
    ]

プリプロセス

今回はただの命令ではない、プリプロセッサ命令(preprocessor directive)についてです。

  1. プリプロセッサ命令って何?
  2. プリプロセッサは何をする?
  3. 仕事 #1 条件コンパイル
  4. 仕事 #2 ファイル結合
  5. テクニック #1 インクルード・ガード
  6. おわりに

プリプロセッサ命令って何?

プリプロセッサ命令というのはHSP独自の呼び方で、一般的にはプリプロセッサ・ディレクティブといいます。
名前の最初についている記号 # が目印です。
# ちなみに、この記号 # はナンバーサインです。シャープ(♯)ではありません。
# いくつかの言語ではコメントを表す記号なので、このように、コメントとしてもよく使われます (HSPの「;」や「//」と同じ)。

このプリプロセッサ命令ですが、一番重要な点は「実行するときには何もしない」ということです。
「実行するとき」ではなく——実行する前に、プリプロセッサ命令の仕事はなされるのです。

HSPを実行する手順は、大きく分けて次の3つに分かれています。

  1. プリプロセス
  2. 中間コード生成
  3. 中間コードの実行
前の2つを合わせて「コンパイル」と呼びます。プリプロセッサ命令はこのタイミングで働きます。


プリプロセッサは何をする?

HSPのプリプロセッサの役割は大きく分けて3通りほどありますが、今回は「スクリプトを機械的に変更する」ものたちをみていきましょう。
文章で説明してもわかりにくいので、ここからはスクリプトを交えて説明してみます。


仕事 #1 条件コンパイル

プリプロセッサが行う代表的な仕事の1つに、条件コンパイルがあります。
与えられた条件によって、スクリプトの一部を削除し、動作を変更するものです。

プリプロセッサ命令 #if は、引数が 0 (= ) なら、#endif までを無視する命令です。
if 文と似たような感じですが、前述のとおり #if は実行する前に処理されるため、条件式には「静的な式」(実行しなくても計算できる式)だけを書くことができます。変数などは使用できません。

// 条件コンパイル #1

#if 0 // 偽

	mes "このメッセージは表示されません"

#endif

	mes "Hello, world!"
	
	stop

この例では、#if 0 から #endif までの間が無視されて、その間にあるスクリプトが実行されません。

一方、次の例では条件式が真 (= 0でない値) なので、#if 1 から #endif の間は有効です。

// 条件コンパイル #2

#if (1 < 2)// この式は

	mes "このメッセージも表示されます"

#endif

	mes "Hello, world!"
	
	stop

続いて、#else を使ってみましょう。
これは単純に、else 命令のプリプロセッサ命令版です。

// 条件コンパイル #3

#if 1 // 

	mes "Hello, world!"

#else

	mes "このメッセージは表示されません"
	
#endif

	stop

実際のところ、#if はあまりHSPでは使われていません。
重要なのは if の亜種である、#ifdef, #ifndef です。
これらの説明の前に、まずマクロ作成講座前半部分を読んでみてください。(「特殊展開マクロ」は読まなくていいです。)

さて、マクロについてわかりましたか。
#ifdef は、マクロが「定義されているとき」という条件の #if 命令です。[脚注]
#ifndef はその否定、つまり「マクロが定義されていないとき」[脚注]になります。

// 条件コンパイル #4

#define MACRO_X	// 定義する

#ifdef MACRO_X	// 定義済み

	mes "MACRO_X が定義されています"

#endif

	mes "Hello, world!"
	
	stop

MACRO_X が定義されている場合、スクリプトが有効です。
MACRO_X は直前で定義されているので、当然これは両方の mes が実行されますね。

// 条件コンパイル #5

;#define MACRO_X	// 定義しない

#ifdef MACRO_X	// 未定義

	mes "MACRO_X が定義されています"

#endif

	mes "Hello, world!"
	
	stop

MACRO_X の定義を消すと、#ifdef の条件は偽ということになり、その中のスクリプトが無視されます。

ちなみに、マクロで定義された定数値は、変数と違ってプリプロセスの時点で値が決定しているため、#if の式に使用することができます。

// 条件コンパイル #6

	text = "strrep 命令はありません。"

//strrep 命令は、HSP3.5から標準命令として追加された。
#if __hspver__ >= 0x3500

	strrep text, "ません", "ます"
	
#endif

	mes text

仕事 #2 ファイル結合

プログラミングでは普通、ソースコードは一定の単位で分割して、ソースコード(スクリプト)を複数のファイルに保存するものです。
すると、プログラミング言語には「他のファイルを参照する機能」が必要になります。
HSPにおけるその機能は、プリプロセッサ命令 #include です。

といっても難しいことをするわけではありません。
#include 命令にファイルを指定すると、そのファイルの中身をスクリプトに貼り付けてくれます。

// ファイル結合

#include "hsp3imp.as"		// common フォルダにあるファイル

#ifdef hspini			// hsp3imp.as で定義されるマクロの1つ
	mes "hsp3imp.as が結合されています。"
#else
	mes "hsp3imp.as が結合されていません。"
#endif
	
	stop

これのプリプロセッサ命令を処理すると、次のような感じになります。

// ファイル結合


;	hsp3imp.dll header

#uselib "hsp3imp.dll"
#func global hspini hspini 0
#func global hspbye hspbye 0
#func global hspprm hspprm 0
#func global hspexec hspexec 0



	mes "hsp3imp.as が結合されています。"

	

	
	stop

※hsp3imp.as の中のプリプロセッサ命令まで消すと、意味不明になるので、これらは残しておきました。 hsp3imp.as を選んだ理由は、短かったからです。

イメージをつかめたでしょうか。
#include は指定したファイルに置き換わる、と覚えておきましょう。

ちなみに、HSPには #include に似た存在として #addition があります。
#include では、指定したファイルが見つからなければエラーとなりますが、#addition の場合は単に何も起こりません。
おそらく使わないので、覚えておく必要はないと思います。


テクニック #1 インクルード・ガード

ここまでに書いた知識を使って、面白いことができます。
インクルード・ガード(include guard)、つまり多重結合防止です。

// インクルード・ガード main

#include "header.as"

#include "header.as"	// 同じファイルを複数結合する

	mes "header.as を2回結合してみました。"
	stop
// インクルード・ガード header

// ※ファイル名は header.as とする。

#define MACRO_X		// 便利なマクロを定義

これで、main の方のスクリプトを実行すると、どうなるでしょうか。
すぐに答えを言うと、エラーが出てコンパイルに失敗します。
これは、「header.as」で定義されているマクロが、すべて重複定義されてしまうからです。

よく使うものを1つのファイルにまとめておき、使いたいときに #include する、というのは非常によくあることです。[脚注]
同じファイルを2個も include してしまう、というのは一見ただのミスのように見えますが、「include したファイルが他に何か include していて、さらにそれも何か他のファイルを include していて、……」という連鎖構造があると、同じファイルを複数回結合してしまう、ということも起こりえます。むしろ日常茶飯事です。
このような問題を防ぐためには、次のようなインクルード・ガードを書きます。

// インクルード・ガード 見本

#ifndef ig_header_as		// このマクロ名はファイル名と同じにするとよい [脚注]
#define ig_header_as /* ファイルの内容 */ #endif

見ればわかるかもしれませんが、動作原理は単純で、「1回読み込むと、2回目以降はファイル全体が飛ばされてしまう」というものです。
まず始めに、#ifndef でマクロが定義されているかどうか調べます。この時点では未定義なので、ここでの #ifndef は何もしません。
次に、#define#ifndef の時と同じマクロを定義します。
そして、このファイルが2回目以降に結合されたときは、#ifndef の時点でマクロが定義済みとなり、ファイル全体が無視されます。
よって、多重結合が行われることはありません。

さっきの例にインクルード・ガードを追加すると次のようになります。

// インクルード・ガード header #2

// ※ファイル名は header.as とする。

#ifndef ig_header_as
#define ig_header_as

#define MACRO_X		// 便利なマクロを定義

#endif

これなら、このファイルを何度 #include しても問題ありません。


おわりに

今回は、基本的なプリプロセッサ命令の使い方を扱ってみました。
途中で書いたとおり、HSPのプリプロセッサには、他にも「モジュール」「命令定義」「コンパイラへのオプション指定」などの機能があります。というか、そっちのほうが重要だったりします。
では、また次回