Macros は、名前付きのコードセグメントで、プログラム内で検出されるたびに、その名前に置き換えられます。
マクロは、言語を拡張し、再利用可能なコードを作成する、強力な方法を提供します。
マクロが使われる理由の1つは、パフォーマンスです。
これらは常にインラインで展開されるため、手続き呼び出しのオーバーヘッドを排除する方法です。
FreeBASIC では、インライン手続きをサポートしていないため、マクロに代わるものはありません。
マクロ定義
プリプロセッサは、文字列置換メカニズムの中に、置換される識別子に、提供されたパラメータを使えます。
これらのパラメーターは、置換文字列で、変更せずに置換されます。
置換文字列は、マクロと呼ばれます。
マクロを定義する構文は次のとおりです('
Preprocessor commands' を参照):
- one-line macro:
#define identifier([parameters]) body
parameters は、定義を関数のようなマクロに変換し、文字列引数をマクロに渡すことができます。
identifier の直後に開き括弧(()を挿入し、その間に空白を入れないでください。そうしないと、コンパイラーはそれを bodyの一部として扱います。
- multi-line macro:
# stringize 演算子を、マクロ・パラメーターで使って、マクロ・パラメーターを文字列リテラルに変換できます。また、
## concatenate 演算子を使って、トークンをマージできます。
('
Preprocessor Operators' を参照)
定義とマクロは、
scope されます(これらは、定義されたスコープでのみ、表示されます)。
一方、名前空間は、定義とマクロの可視性に、影響を与えません。
マクロのメカニズムにより、すべての型で機能する、一般的な手続きと同等のことができます。
ただし、マクロに渡されるパラメーターは、マクロ定義で使用されるたびに、マクロによって評価されることに、注意する必要があります。
これにより、パフォーマンスの問題が発生したり、さらに悪いことに、望ましくないエッジ効果(境界部分が外部からの影響を受けること)が発生する可能性があります。
括弧は、常にマクロのパラメーターを囲む必要があります:
- 実際、これらのパラメーターは複合式にすることができ、マクロで使う前に、完全に計算する必要があります。
- 括弧は、この計算を強制します。
- 設定されていないと、優先度ルールは、マクロ自体で、論理エラーを生成する可能性があります。
同様に、値を返すマクロは、別の式で使う前に、完全な評価を強制するために、括弧で囲む必要があります。
正しくないマクロと、正しいマクロの例:
#define MUL1(x, y) x * y '' incorect macro (parameters must be enclosed in parentheses)
#define MUL2(x, y) ( x ) * ( y ) '' incorrect macro (and returned result must also be in parentheses)
#define MUL3(x, y) ( ( x ) * ( y ) ) '' correct macro
Print MUL1(5-3, 1+2)^2 '' 6 (incorrect result)
Print MUL2(5-3, 1+2)^2 '' 18 (incorrect result)
Print MUL3(5-3, 1+2)^2 '' 36 (correct result)
Sleep
したがって、括弧は、マクロの一貫した動作を保証します(括弧は、マクロ定義に追加しますが、絶対に必要です)。
マクロのデバッグ
マクロを使うことは、極めて安全ではなく、見つけるのが非常に難しい、多くの落とし穴を隠します。
手続きは、型チェックとスコープを提供しますが、マクロは、渡された引数を、置き換えるだけです。
マクロのもう 1つの欠点は、プログラムのサイズです。
その理由は、プリ-プロセッサは、プログラムのコンパイル・プロセスの前に、プログラム内のすべてのマクロを、実際の定義に、置き換えるからです。
ソース・コード・ファイルだけを見て、問題の原因を見つける唯一の方法は、マクロの定義を見て、何が起こったかを理解することです。
マクロを使う場合の最も一般的なエラーは、不均衡な開き括弧です(コンパイル時に、エラーが発生します)。
もう1つは、マクロ定義で、引数(または、存在する場合は戻り値)を括弧で囲むことを忘れることです。
これは、演算子の優先順位のために、かなり厄介な副作用を引き起こす可能性があります(コンパイル時のエラーや実行時のバグを引き起こします)。
コンパイラーは(展開後に)マクロ内でエラーを検出すると、素朴なエラー・メッセージを提示します。含まれるのは次のものだけです:
- マクロの呼び出しの行番号、
- エラーの種類、
- (マクロの)呼び出しの文字列。
エラーが明らかでない場合(不鮮明な報告エラーの場合)、唯一の解決策(コール行番号のみ)は、現在、修正が成功するまで、次の 5つのステップを繰り返し実行することです:
Do
1. ソース・ファイルで fbc を呼び出しますが、'-pp' コンパイル・オプションを使います(fbc コマンドは、コンパイルせずに、前処理済みの入力ファイルのみを出力します)。
2. 前処理されたファイルを復元します。
3. この前処理されたファイルから、直接編集およびコンパイルします。
4. エラーを分析し、理解し、修正し、修正された前処理済みファイルを再度コンパイルします。
5. 元のソース・ファイルの、関連するマクロ本文で、同等の修正を後回しにします。
Loop
短いコードのエラーの例:
- Source file (*.bas):
#macro FIRST(array, Operator)
For index As Integer = LBound(array) To UBound(array) - 1
Print " " Operator array(index) Operator ",";
Next index
Print array(UBound(array))
#endmacro
#macro Second(array)
Print #array + ":"
FIRST(array, +)
#endmacro
Dim As String test1(0 To ... ) => {"First", "Second", "Third"}
Dim As Integer test2(0 To ...) => {1, 2 ,3}
Second(test1)
Print
Second(test2)
Sleep
Compiler output:
...\FBIDETEMP.bas(18) error 20: Type mismatch, found '+' in 'SECOND(test2)'
- Pre-processed file (*.pp.bas):
Dim As String test1(0 To ... ) => {"First", "Second", "Third"}
Dim As Integer test2(0 To ...) => {1, 2 ,3}
Print $"test1" + ":"
For index As Integer =LBound(test1) To UBound(test1) -1
Print " " + test1(index) + ",";
Next index
Print test1(UBound(test1))
Print
Print $"test2" + ":"
For index As Integer =LBound(test2) To UBound(test2) -1
Print " " + test2(index) + ",";
Next index
Print test2(UBound(test2))
Sleep
Compiler output:
...\FBIDETEMP.bas(14) error 20: Type mismatch, found '+' in 'Print " " + test2(index) + ",";'
- Example macro correction in source code (*.bas):
#macro FIRST(array, Operator)
For index As Integer = LBound(array) To UBound(array) - 1
Print " " Operator array(index) Operator ",";
Next index
Print array(UBound(array))
#endmacro
#macro Second(array)
Print #array + ":"
FIRST(array, &) ''corrected line ("&" instead of "+")
#endmacro
Dim As String test1(0 To ... ) => {"First", "Second", "Third"}
Dim As Integer test2(0 To ...) => {1, 2 ,3}
Second(test1)
Print
Second(test2)
Sleep
出力:
test1:
First, Second,Third
test2:
1, 2, 3
注: もう1つの解決策は、マクロの呼び出しに関連する、コンパイラからのより詳細なエラーメッセージです(機能リクエストに既に登録されている、改善されたエラーメッセージの提案)。
可変長のマクロ
#macro か
#define 宣言の最後のパラメータの後ろに省略記号
"..." (3つの点)を使うと、可変長のマクロを作成できます:
or:
#define identifier([parameters,] variadic_parameter...) body
そのため、
variadic_parameter を通じて任意の数の引数を渡すことができ、それらは通常のマクロ・パラメータであるかのように本体で使うことができます。
マクロ展開時には、マクロの置換リストにある
variadic_parameter の各出現箇所が、渡された引数で置き換えられます。
variadic_parameter は、カンマを含む渡された引数の完全なリストに展開され、完全に空にすることもできます。
可変引数リスト内の個々の引数に、再帰的にアクセスするための直接的な手段は提供されていません。
variadic_parameter によって渡されるさまざまな引数を区別するには、最初に
# stringize 演算子を使って
variadic_parameter を文字列リテラルに変換し、次にこの文字列リテラル (
#variadic_parameter) で渡された各引数を区切り文字 (コンマ) で区別します。
例
1行と、複数行のマクロの例:
#define MIN(x, y) IIf( ( x ) < ( y ), x, y ) '' maximum function-like macro
#define MAX(x, y) IIf( ( x ) > ( y ), x, y ) '' minimum function-like macro
#define MUL(x, y) ( ( x ) * ( y ) ) '' multiply function-like macro
#macro REV_STR(str_dest, str_src) '' reverse-string sub-like macro
For i As Integer = Len(str_src) To 1 Step -1
str_dest &= Mid(str_src, i, 1)
Next I
#endmacro
Print MIN(5 - 3, 1 + 2) '' 2
Print MAX(5 - 3, 1 + 2) '' 3
Print MUL(5 - 3, 1 + 2)^2 '' 36
Dim As String s
REV_STR(s, "CISABeerF") '' FreeBASIC
Print s
Sleep
可変長のマクロを使った例:
' 複数のサブパラメータを含むことができる可変パラメータを持つマクロ:
' variadic_parameter で渡される異なる引数を区別するため、
' 演算子 # (プリプロセッサ Stringize)を使って、まず variadic_parameter を文字列に変換します。
' そして、この文字列(#variadic_parameter)の中で、渡された各引数をセパレータ(通常はコンマ)の位置で区別します。
#macro average(result, arg...)
Scope
Dim As String s = #arg
If s <> "" Then
result = 0
Dim As Integer n
Do
Dim As Integer k = InStr(1, s, ",")
If k = 0 Then
result += Val(s)
result /= n + 1
Exit Do
End If
result += Val(Left(s, k - 1))
n += 1
s = Mid(s, k + 1)
Loop
End If
End Scope
#endmacro
Dim As Double result
average(result, 1, 2, 3, 4, 5, 6)
Print result
' Output : 3.5
参照