Macros
Macros are named code segments that are substituted to their names each time they are encountered in a program.
Macros offer a powerful way to extend the language and create reusable code.
One reason macros are used is performance.
They are a way of eliminating procedure call overhead because they are always expanded in-line.
There is no alternative for this in FreeBASIC because it does not support inline procedures.
Macros definition
The preprocessor can, during the text replacement mechanism, use parameters supplied to the identifier to be replaced.
These parameters are then replaced without modification in the replacement text.
The replacement text is then called macro.
The syntax to define a macro is as follows (see 'Preprocessor commands'):
(see 'Preprocessor Operators')
Defines and macros are scoped (they are only visible in the scope they were defined in).
Namespaces on the other hand do not have any effect on the visibility of the defines and macros.
The mechanism of macros allows to do the equivalent of general procedures, which work for all types.
However, care must be taken that parameters passed to a macro are evaluated by it each time they are used in the macro definition. This can cause performance issues or, worse, cause unwanted edge effects.
Parentheses should always be placed around the parameters of the macro:
Example of incorrect and correct macros:
These parameters are then replaced without modification in the replacement text.
The replacement text is then called macro.
The syntax to define a macro is as follows (see 'Preprocessor commands'):
- one-line macro:
- multi-line macro:
The # stringize operator can be used on macro parameters to turn them into string literals, and the ## concatenate operator can be used to merge tokens together.
#define identifier([parameters]) body
parameters turn a define into a function-like macro, allowing text arguments to be passed to the macro.
identifier should be followed by the opening parentheses (() immediately without any white-space in between, otherwise the compiler will treat it as part of the body.
identifier should be followed by the opening parentheses (() immediately without any white-space in between, otherwise the compiler will treat it as part of the body.
(see 'Preprocessor Operators')
Defines and macros are scoped (they are only visible in the scope they were defined in).
Namespaces on the other hand do not have any effect on the visibility of the defines and macros.
The mechanism of macros allows to do the equivalent of general procedures, which work for all types.
However, care must be taken that parameters passed to a macro are evaluated by it each time they are used in the macro definition. This can cause performance issues or, worse, cause unwanted edge effects.
Parentheses should always be placed around the parameters of the macro:
- Indeed, these parameters can be compound expressions, which must be computed completely before being used in the macro.
- Parentheses force this calculation.
- If they are not set, priority rules can generate a logic error in the macro itself.
Similarly, macros that return a value should be surrounded by parentheses, in order to force their complete evaluation before using them in another expression.- Parentheses force this calculation.
- If they are not set, priority rules can generate a logic error in the macro itself.
Example of incorrect and correct macros:
#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
Thus, the parentheses ensure a consistent behavior of the macro (parentheses can add to macro definitions, but they are absolutely necessary).#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
Macros debug
Using macros can be extremely unsafe and they hide a lot of pitfalls which are very hard to find.
Procedures give type checking and scoping, but macros just substitute the passed argument.
Another disadvantage of the macro is the size of the program. The reason is, the pre-processor will replace all the macros in the program by its real definition prior to the compilation process of the program.
Looking only at the source code file, the only way to find out what the problem is to look at the definition of the macro and try to understand what happened.
The most common error when using macros is the unbalanced open parentheses (inducing error at compile-time).
Another is to forget putting parentheses around arguments (or returned result if exists) in macro definitions. That can cause some pretty nasty side effects because of operator precedence (inducing error at compile-time or bug at run-time).
When the compiler detects an error inside a macro (after expanding), it provides a rustic error message containing only:
Procedures give type checking and scoping, but macros just substitute the passed argument.
Another disadvantage of the macro is the size of the program. The reason is, the pre-processor will replace all the macros in the program by its real definition prior to the compilation process of the program.
Looking only at the source code file, the only way to find out what the problem is to look at the definition of the macro and try to understand what happened.
The most common error when using macros is the unbalanced open parentheses (inducing error at compile-time).
Another is to forget putting parentheses around arguments (or returned result if exists) in macro definitions. That can cause some pretty nasty side effects because of operator precedence (inducing error at compile-time or bug at run-time).
When the compiler detects an error inside a macro (after expanding), it provides a rustic error message containing only:
- the line number where is the call of the macro,
- the error type,
- the text of the call (of the macro).
When the error is not obvious (type of error reported blurred), the only solution (with only the call line number) is presently to iteratively execute the following 5 steps until correction successful:- the error type,
- the text of the call (of the macro).
Do
Example of error on a short code:
1. call fbc on the source file, but with the '-pp' compile option (fbc command emitting only the pre-processed input file, without compiling),
2. recover the pre-processed file,
3. edit and compile directly from this pre-processed file,
4. analyze the error, understand it, correct it, and compile again the pre-processed file thus modified,
5. postpone the equivalent correction in the concerned macro body of the original source file.
Loop2. recover the pre-processed file,
3. edit and compile directly from this pre-processed file,
4. analyze the error, understand it, correct it, and compile again the pre-processed file thus modified,
5. postpone the equivalent correction in the concerned macro body of the original source file.
- Source file (*.bas):
- Pre-processed file (*.pp.bas):
- 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, +)
#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: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)
Second(test2)
Sleep
...\FBIDETEMP.bas(18) error 20: Type mismatch, found '+' in 'SECOND(test2)'
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: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 $"test2" + ":"
For index As Integer =LBound(test2) To UBound(test2) -1
Print " " + test2(index) + ",";
Next index
Print test2(UBound(test2))
Sleep
...\FBIDETEMP.bas(14) error 20: Type mismatch, found '+' in 'Print " " + test2(index) + ",";'
#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
Output: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)
Second(test2)
Sleep
test1: First, Second,Third test2: 1, 2, 3
Note: Another solution could be a more detailed error message from compiler, relating to the call of the macro (proposal of improved error message already filled in in a feature request).
Variadic macros
Using an ellipsis "..." (3 dots) behind the last parameter in a #macro or #define declaration allows creation of a variadic macro:
During macro expansion each occurrence of the variadic_parameter in the macro replacement list is replaced by the passed arguments. The variadic_parameter will expand to the full list of arguments passed to it, including commas, and can also be completely empty.
No direct means is provided to recursively access individual arguments in the variable argument list.
To distinguish between the different arguments passed by the variadic_parameter, one can first convert the variadic_parameter to a string literal using the # stringize operator, then differentiate in this string literal (#variadic_parameter) each passed argument by locating the separators (a comma).
or:
So, it is possible to pass any number of arguments through the variadic_parameter, which can be used in the body as if it was a normal macro parameter.During macro expansion each occurrence of the variadic_parameter in the macro replacement list is replaced by the passed arguments. The variadic_parameter will expand to the full list of arguments passed to it, including commas, and can also be completely empty.
No direct means is provided to recursively access individual arguments in the variable argument list.
To distinguish between the different arguments passed by the variadic_parameter, one can first convert the variadic_parameter to a string literal using the # stringize operator, then differentiate in this string literal (#variadic_parameter) each passed argument by locating the separators (a comma).
Examples
Example with one-line and multi-line macros:
Example with variadic macro:
See also
#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
#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
' macro with a variadic parameter which can contain several sub-parameters:
' To distinguish between the different arguments passed by variadic_parameter,
' you can first convert variadic_parameter to a string using the Operator # (Preprocessor Stringize),
' then differentiate in this string (#variadic_parameter) each passed argument by locating the separators (usually a comma).
#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
' To distinguish between the different arguments passed by variadic_parameter,
' you can first convert variadic_parameter to a string using the Operator # (Preprocessor Stringize),
' then differentiate in this string (#variadic_parameter) each passed argument by locating the separators (usually a comma).
#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
Back to Programmer's Guide