Conditional compilation: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | **Conditional compilation** refers to compiling or omitting parts of [[Source code|source code]] based on an [[compile time expressions|expression evaluated at compile-time]]. This allows taking account of, for example, different interfaces or architectures of specific [[operating system|operating systems]] or platforms, while still being able to program in a generic way. | |
| 2 | 2 | ||
| 3 | 3 | ## Support | |
| 4 | 4 | ||
| 5 | 5 | Conditional compilation needs to be supported in some way or other. Some [[Compiler|compilers]] need an additional tool called *pre-processor*, [[FPC]] however has all required functionality built-in. | |
| 6 | 6 | ||
| 7 | 7 | For FPC and in de-facto most compiled languages, conditional compilation is implemented by specially crafted comments that are then seen as [[Compiler directive|compiler directives]]. These surround any arbitrary amount of code that may be ignored or remain included based on an expression provided as evaluated at [[Compile time|compile-time]]. | |
| 8 | 8 | ||
| 9 | 9 | They can be used for a variety of purposes like: | |
| 10 | 10 | ||
| 11 | 11 | - Platform specific code isolation | |
| 12 | 12 | - Natural language selection (where `resourceString`s do not suffice) | |
| 13 | 13 | - Licensing opensource and closed source parts | |
| 14 | 14 | - Isolating experimental code | |
| 15 | 15 | - Compiler version: certain compiler features may have been present only since a certain version | |
| 16 | 16 | - Library version: interfaces may have changed with certain versions | |
| 17 | 17 | - etc., etc. | |
| 18 | 18 | ||
| 19 | 19 | ## Relevant compiler directives | |
| 20 | 20 | ||
| 21 | 21 | FPC supports four different styles of conditional compilation: | |
| 22 | 22 | ||
| 23 | 23 | - [[Turbo Pascal]] and early [[Delphi]] style directives | |
| 24 | 24 | - [[Mac Pascal]] style directives | |
| 25 | 25 | - Modern Free Pascal and Delphi style directives | |
| 26 | 26 | - Compile time Macros | |
| 27 | 27 | ||
| 28 | 28 | Note the syntax here is not [[case-sensitive|case sensitive]] as conforms to all [[Pascal]] syntax. We will use both lowercase and uppercase examples. We will show you the difference between the modes and how to efficiently use them. | |
| 29 | 29 | ||
| 30 | 30 | ### Turbo Pascal style directives | |
| 31 | 31 | ||
| 32 | 32 | The Turbo Pascal style directives are | |
| 33 | 33 | ||
| 34 | 34 | - `{$DEFINE}`, | |
| 35 | 35 | - `{$IFDEF}`, | |
| 36 | 36 | - `{$ENDIF}`, | |
| 37 | 37 | - `{$IFNDEF}`, | |
| 38 | 38 | - `{$IFOPT}`, | |
| 39 | 39 | - `{$ELSE}`, | |
| 40 | 40 | - `{$ELSEIF}` and | |
| 41 | 41 | - `{$UNDEF}`. | |
| 42 | 42 | ||
| 43 | 43 | We will describe the directives in the context of the style. Some defines have an extended meaning in another style. | |
| 44 | 44 | ||
| 45 | 45 | That means later on we may expand the meaning of certain directives like e. g. `{$DEFINE}`in the context of Macros. | |
| 46 | 46 | ||
| 47 | 47 | #### `$define` | |
| 48 | 48 | ||
| 49 | 49 | The `{$DEFINE}` directive simply declares a symbol that we later can use for conditional compilation: | |
| 50 | 50 | ||
| 51 | 51 | ``` pascal | |
| 52 | 52 | {$DEFINE name} // This defines a symbol called "name" | |
| 53 | 53 | ``` | |
| 54 | 54 | ||
| 55 | 55 | Note you can also define a symbol from the [[Command-line interface|command line]] or the [[IDE]], for example | |
| 56 | 56 | ||
| 57 | 57 | ``` bash | |
| 58 | 58 | -dDEBUG | |
| 59 | 59 | ``` | |
| 60 | 60 | ||
| 61 | 61 | is the command line equivalent of | |
| 62 | 62 | ||
| 63 | 63 | ``` pascal | |
| 64 | 64 | {$DEFINE DEBUG} | |
| 65 | 65 | ``` | |
| 66 | 66 | ||
| 67 | 67 | in the source code. | |
| 68 | 68 | ||
| 69 | 69 | #### `$undef` | |
| 70 | 70 | ||
| 71 | 71 | The `{$UNDEF}` directive undefines a (presumably) previously defined symbol. Here is an example that the author uses in practice: | |
| 72 | 72 | ||
| 73 | 73 | ``` pascal | |
| 74 | 74 | // Some older source code is polluted with {$IFDEF FPC} | |
| 75 | 75 | // that are no longer necessary | |
| 76 | 76 | // depending on the Delphi version to which it it should be compatible. | |
| 77 | 77 | // I always test this by trying this on top of the program or unit: | |
| 78 | 78 | {$IFDEF FPC} | |
| 79 | 79 | {$MODE DELPHI} | |
| 80 | 80 | {$UNDEF FPC} | |
| 81 | 81 | {$DEFINE VER150} | |
| 82 | 82 | // code will now compile as if it was Delphi 7, | |
| 83 | 83 | // provided the original Delphi source code | |
| 84 | 84 | // was indeed written for Delphi 7 and up. | |
| 85 | 85 | {$ENDIF} | |
| 86 | 86 | ``` | |
| 87 | 87 | ||
| 88 | 88 | #### `$ifdef` and `$endif` | |
| 89 | 89 | ||
| 90 | 90 | The simplest way to define a block of conditional code is like this: | |
| 91 | 91 | ||
| 92 | 92 | ``` pascal | |
| 93 | 93 | unit cross; | |
| 94 | 94 | {$IFDEF FPC}{$MODE DELPHI}{$ENDIF} | |
| 95 | 95 | ``` | |
| 96 | 96 | ||
| 97 | 97 | The above example is quite common for source code that has to compile with both Delphi and FPC. | |
| 98 | 98 | ||
| 99 | 99 | If the compiler is Delphi, then nothing is done, but if the compiler is the FPC, it will configure FPC to compile and use Delphi syntax mode. | |
| 100 | 100 | ||
| 101 | 101 | This `FPC` conditional symbol is defined by the compiler (cf. [`compiler/options.pas`](https://svn.freepascal.org/cgi-bin/viewvc.cgi/tags/release_3_2_0/compiler/options.pas?view=markup#l3695)). The `{$IFDEF}` and `{$ENDIF}` frame syntax is symmetrical: Every `{$IFDEF}` has a matching `{$ENDIF}`. | |
| 102 | 102 | ||
| 103 | 103 | To help you recognize the corresponding blocks you can use e. g. indentation, but you can also use the [[Comments|comment]] feature: | |
| 104 | 104 | ||
| 105 | 105 | ``` pascal | |
| 106 | 106 | {$IFDEF FPC this part is Free Pascal specific} | |
| 107 | 107 | // some Free Pascal specific code | |
| 108 | 108 | {$ENDIF Free Pascal specific code} | |
| 109 | 109 | ``` | |
| 110 | 110 | ||
| 111 | 111 | > **Warning:** This comment feature is often not well understood. Some people – as on an older version of this wiki entry – assumed you could nest `{$IFDEF}` because the compiler seems to accept the syntax. But the former is false and the latter is true: Yes, the compiler accepts the syntax below, but it is not a nested `{$IFDEF}` but a single {`$IFDEF}` condition and the rest is a comment! | |
| 112 | 112 | > | |
| 113 | 113 | > The code below executes the [[Write|writeLn]] if and only if `red` is defined. In this example `{$ifdef blue}` is a comment! Even if the `{$define blue}` is valid. | |
| 114 | 114 | > | |
| 115 | 115 | > ``` pascal | |
| 116 | 116 | > // program completely wrong; | |
| 117 | 117 | > {$define blue} | |
| 118 | 118 | > begin | |
| 119 | 119 | > {$ifdef red or $ifdef blue} // everything after red is a comment | |
| 120 | 120 | > writeLn ('red or blue'); // this code is never reached | |
| 121 | 121 | > {$endif red or blue} // everything after $endif is a comment. | |
| 122 | 122 | > end. | |
| 123 | 123 | > ``` | |
| 124 | 124 | ||
| 125 | 125 | > **Note:** The comment feature is non-standard and the [[GNU Pascal|GPC]] for instance will emit a warning “`` garbage at end of `$ifdef' argument ``”. | |
| 126 | 126 | ||
| 127 | 127 | #### `$ifndef` | |
| 128 | 128 | ||
| 129 | 129 | This is the opposite of `{$IFDEF}` and code will be included of a certain condition is *not* defined. A simple example is: | |
| 130 | 130 | ||
| 131 | 131 | ``` pascal | |
| 132 | 132 | {$IFNDEF FPC this part not for Free Pascal} | |
| 133 | 133 | // some specific code that Free Pascal should not compile | |
| 134 | 134 | {$ENDIF code for other compilers than Free Pascal} | |
| 135 | 135 | ``` | |
| 136 | 136 | ||
| 137 | 137 | #### `$else` and `$elseif` | |
| 138 | 138 | ||
| 139 | 139 | `{$ELSE}` is used to compile code that does not belong to the code block that is defined by the corresponding `{$IFDEF}`. It is also valid in the context `{$IFOPT}`, `{$IF}` or `{$IFC}` that we will discuss later. | |
| 140 | 140 | ||
| 141 | 141 | ``` pascal | |
| 142 | 142 | {$IFDEF red} | |
| 143 | 143 | writeLn('Red is defined'); | |
| 144 | 144 | {$ELSE no red} | |
| 145 | 145 | {$IFDEF blue} | |
| 146 | 146 | writeLn('Blue is defined, but red is not defined'); | |
| 147 | 147 | {$ELSE no blue} | |
| 148 | 148 | writeLn('Neither red nor blue is defined'); | |
| 149 | 149 | {$ENDIF blue} | |
| 150 | 150 | {$ENDIF red} | |
| 151 | 151 | ``` | |
| 152 | 152 | ||
| 153 | 153 | Such nested conditional written in the above syntax can get very confusing and thus is prone to errors. Luckily we can simplify it a lot by using `{$ELSEIF}`. The code below is an expanded equivalent of the first example: | |
| 154 | 154 | ||
| 155 | 155 | ``` pascal | |
| 156 | 156 | {$IF defined(red)} | |
| 157 | 157 | writeLn('Red is defined'); | |
| 158 | 158 | {$ELSEIF defined(blue)} | |
| 159 | 159 | writeLn('Blue is defined'); | |
| 160 | 160 | {$ELSEIF defined(green)} | |
| 161 | 161 | writeLn('Green is defined'); | |
| 162 | 162 | {$ELSE} | |
| 163 | 163 | writeLn('Neither red, blue or green. Must be black...or something else...'); | |
| 164 | 164 | {$ENDIF} | |
| 165 | 165 | ``` | |
| 166 | 166 | ||
| 167 | 167 | As you can see this is a lot more readable. | |
| 168 | 168 | ||
| 169 | 169 | #### `$ifopt` | |
| 170 | 170 | ||
| 171 | 171 | With `{$IFOPT}` we can check if a certain compile option is defined. | |
| 172 | 172 | ||
| 173 | 173 | From the programmers’ manual: | |
| 174 | 174 | ||
| 175 | 175 | > The `{$IFOPT switch}` will compile the text that follows it if the switch switch is currently in the specified state. If it isn’t in the specified state, then compilation continues after the corresponding `{$ELSE}` or `{$ENDIF}` directive. | |
| 176 | 176 | ||
| 177 | 177 | As an example: | |
| 178 | 178 | ||
| 179 | 179 | ``` pascal | |
| 180 | 180 | {$IFOPT M+} | |
| 181 | 181 | writeLn('Compiled with type information'); | |
| 182 | 182 | {$ENDIF} | |
| 183 | 183 | ``` | |
| 184 | 184 | ||
| 185 | 185 | Will compile the `writeLn` statement only if generation of type information is enabled. | |
| 186 | 186 | ||
| 187 | 187 | > **Note:** The `{$IFOPT}` directive accepts only short options, i. e. `{$IFOPT TYPEINFO}` will not be accepted. | |
| 188 | 188 | ||
| 189 | 189 | A common use is this example to test if `DEBUG` mode is defined: | |
| 190 | 190 | ||
| 191 | 191 | ``` pascal | |
| 192 | 192 | {$IFOPT D+}{$NOTE debug mode is active}{$ENDIF} | |
| 193 | 193 | ``` | |
| 194 | 194 | ||
| 195 | 195 | Such defines can also reside in configuration files like `fpc.cfg` which also contains a full explanation on how to use: | |
| 196 | 196 | ||
| 197 | 197 | ```text | |
| 198 | 198 | # ---------------------- | |
| 199 | 199 | # Defines (preprocessor) | |
| 200 | 200 | # ---------------------- | |
| 201 | 201 | # | |
| 202 | 202 | # nested #IFNDEF, #IFDEF, #ENDIF, #ELSE, #DEFINE, #UNDEF are allowed | |
| 203 | 203 | # | |
| 204 | 204 | # -d is the same as #DEFINE | |
| 205 | 205 | # -u is the same as #UNDEF | |
| 206 | 206 | # | |
| 207 | 207 | # | |
| 208 | 208 | # Some examples (for switches see below, and the -? help pages) | |
| 209 | 209 | # | |
| 210 | 210 | # Try compiling with the -dRELEASE or -dDEBUG on the command line | |
| 211 | 211 | # | |
| 212 | 212 | # For a release compile with optimizes and strip debug info | |
| 213 | 213 | #IFDEF RELEASE | |
| 214 | 214 | -O2 | |
| 215 | 215 | -Xs | |
| 216 | 216 | #WRITE Compiling Release Version | |
| 217 | 217 | #ENDIF | |
| 218 | 218 | ``` | |
| 219 | 219 | ||
| 220 | 220 | ## What not to do | |
| 221 | 221 | ||
| 222 | 222 | This is a short tutorial: | |
| 223 | 223 | ||
| 224 | 224 | ### Cases | |
| 225 | 225 | ||
| 226 | 226 | What is wrong with this code? Can you spot it? | |
| 227 | 227 | ||
| 228 | 228 | ``` pascal | |
| 229 | 229 | var | |
| 230 | 230 | MyFilesize: | |
| 231 | 231 | {$ifdef Win32} | |
| 232 | 232 | Cardinal | |
| 233 | 233 | {$else} | |
| 234 | 234 | int64 | |
| 235 | 235 | {$endif} | |
| 236 | 236 | ; | |
| 237 | 237 | ``` | |
| 238 | 238 | ||
| 239 | - | ||
| 240 | - | ||
| 241 | - | ||
| 242 | - | ||
| 243 | - | ||
| 244 | - | ||
| 245 | - | ||
| 246 | - | ||
| 247 | - | ||
| 248 | - | ||
| 249 | - | ||
| 250 | - | ||
| 251 | - | ||
| 252 | - | ||
| 253 | - | ||
| 254 | - | ||
| 255 | - | ||
| 256 | - | ||
| 257 | - | ||
| 258 | - | ||
| 259 | - | ||
| 260 | - | ||
| 261 | - | ||
| 262 | - | ||
| 263 | - | ||
| 264 | - | ||
| 239 | + | answer | |
| 265 | 240 | ||
| 241 | + | The answer is: | |
| 242 | + | ||
| 243 | + | - that Free Pascal compiles for more CPU types than [[32 bit|32]] an [[64 bit]], also for e. g. 8 and 16 bit. | |
| 244 | + | - on most 64-bit platforms the maximum file size is a [[QWord]], not an [[Int64]]. | |
| 245 | + | ||
| 246 | + | That programmer fell into a trap that is common: If you use a define, make sure your logic is solid. Otherwise such code can easily cause accidents. The compiler will not catch your logic errors! | |
| 247 | + | ||
| 248 | + | It is always good to realize such things especially that such things can easily be fixed. | |
| 249 | + | ||
| 250 | + | ``` pascal | |
| 251 | + | var | |
| 252 | + | MyFilesize: | |
| 253 | + | {$if defined(Win32)} | |
| 254 | + | Cardinal | |
| 255 | + | {$elseif defined(Win64)} | |
| 256 | + | Qword; | |
| 257 | + | {$else} | |
| 258 | + | {$error this code is written for win32 or win64} | |
| 259 | + | {$endif} | |
| 260 | + | ``` | |
| 261 | + | ||
| 262 | + | As an aside of course there is a solution for this particular example that does not use conditionals at all: | |
| 263 | + | ||
| 264 | + | ``` pascal | |
| 265 | + | var | |
| 266 | + | MyFilesize: NativeUint; | |
| 267 | + | ``` | |
| 268 | + | ||
| 266 | 269 | ### Understanding | |
| 267 | 270 | ||
| 268 | 271 | What is wrong with this code? Can you spot it? | |
| 269 | 272 | ||
| 270 | 273 | ``` pascal | |
| 271 | 274 | {$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF} | |
| 272 | 275 | {$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF} | |
| 273 | 276 | ``` | |
| 274 | 277 | ||
| 275 | - | ||
| 276 | - | ||
| 277 | - | ||
| 278 | - | ||
| 279 | - | ||
| 280 | - | ||
| 281 | - | ||
| 282 | - | ||
| 283 | - | ||
| 284 | - | ||
| 285 | - | ||
| 286 | - | ||
| 278 | + | answer | |
| 279 | + | ||
| 280 | + | The Answer is: | |
| 281 | + | ||
| 282 | + | - Well, I have already wrote a *comment* that warned you.. so look at the warning.... You should be able to spot it... | |
| 283 | + | - Compiler directives override the compiler... be careful with that ax Eugene. | |
| 287 | 284 | ||
| 288 | 285 | --- | |
| 289 | 286 | *From the Free Pascal wiki, page "Conditional compilation", as it stood on 2020-07-14 after a change by Kai Burghardt, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
