4

The next pattern is common in C code:

#ifndef SOMETHING
#define SOMETHING
#endif

The pattern is possible in Delphi code too:

{$IFNDEF SOMETHING}
{$DEFINE SOMETHING}
{$ENDIF}

but it is not common - I have never seen it at all. If a Delphi code requires a conditional define, it just defines it without IFNDEF check.

Why so? What is the difference in conditional compilation between C and Delphi so that ifndef check is needed for former and not needed for latter?

kludg
  • 27,213
  • 5
  • 67
  • 118

2 Answers2

12

That's because this is not only common but mandatory in C:

#include <something.h>

While this is rarely used in Delphi. And when used, it's actually used to set up those {$DEFINE} 's:

{$INCLUDE 'something.inc'}

This matters because DEFINES are only valid while compiling one object (may it be a .PAS file or a .C file). Delphi uses the uses clause to include other units, while C uses the include to include it's headers. In C headers might themselves include other headers. The pattern you're asking about is used to prevent recursively re-including the same header.

To make maters crystal-clear, here's a sample of what one might use in C, and the equivalent in Delphi. Let's say we've got a 3 files setup, where A needs to include both B and C, and B only needs to include C. The "C" files would look like this:

// ----------------------- A.h
#ifndef A
#define A

#include "B.h"
#include "C.h"

// Stuff that goes in A

#endif

// ------------------------ B.h
#ifndef B
#define B

#include "C.h"

// Stuff that goes in B

#endif

// ----------------------- C.h
#ifndef C
#define C

// Stuff that goes in C

#endif

Without the conditional defines in C.h, the C.h file would end up being included twice in A.h. This is how the code would look like in Delphi:

// --------------------- A.pas
unit A;

interface

uses B, C;

implementation

end.

// --------------------- B.pas
unit B

interface

uses C;

implementation

end.

// --------------------- C.pas

unit C

interface

implementation

end.

The Delphi/Pascal version doesn't need to protect "C" from being included twice in "A" because it doesn't use the {$INCLUDE} to achieve this goal, it use the uses statement. The compiler would get the exported symbols from the B.dcu file and the C.dcu files with no risk of including symbols from C.dcu twice.


Other reasons to see a lot more precompiler directives in C code:

  • The precompiler is a lot more powerful then Delphi's. A {$DEFINE} in Delphi code only deals with conditional compilation, while the C variant can be used for both conditional compilation and as a form of word substitution.
  • The mandatory use of #include for headers means you can have a header that defines macros. Or you can have a header that's configured by specifying some #define statements before the actual #include <header.h>
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • Thorough answer, but your example is not :-) If you add a depencency from C to A the motivation will make more sense. – Christoffer Jan 20 '12 at 07:19
  • Good explanation how to prevent header recursion with `#ifndef`, but that seems to be not the only use of `#ifndef` in C. I have seen many times `#ifndef` used without subsequent `#include`. – kludg Jan 20 '12 at 07:31
  • A dependency from C to A might be more interesting, because without the IFDEF's it would result in an infinite recursive loop. But then I'd have problems with the Delphi sample, since Delphi doesn't allow circular dependencies. And I might get into trouble with design purists because circular dependencies really are a bad design. – Cosmin Prund Jan 20 '12 at 07:33
  • @Serg, in my example the `C.h` file doesn't have any INCLUDE's, yet without the IFNDEF protection it would be included twice in `A.h`. – Cosmin Prund Jan 20 '12 at 07:35
  • I am sure that `#ifndef` is used not only to prevent header recursion, though that is probably most important use of `#ifndef`. I also believe that the importance of `#ifndef` in C is related to extensive use of header files ( *.h) while their Delphi analogs ( *.inc files) are used rarely and with very limited purpose. – kludg Jan 20 '12 at 07:57
  • @Serg, I updated the answer to provide some more information, right down at the bottom. Precompiler use in C code is so much more extensive, because the precompiler really is more useful and because the use of included headers make precompiler (ab)use so much easier. Once you get into the habit of using the precompiler, you'll use the precompiler, and IF(N)DEF's are probably the easiest ways to use it. – Cosmin Prund Jan 20 '12 at 08:03
  • @Serg, thats too many letters, but main idea behind "include guard" is what C have flat module scope, #include includes given file literally and #defined macros affects whole remaining files to process. – OnTheFly Jan 20 '12 at 08:11
  • @user539484 I don't believe that is true, or I don't understand what 'flat module scope' mean. If `#define smth` is not included, either directly or indirectly in compile unit ( *.c file) then 'smth' should be undefined. – kludg Jan 20 '12 at 08:24
  • @Serg, lets approach that from the other side: DCC forgets any $defined conditional symbols at the end of module. It is documented behaviour. – OnTheFly Jan 20 '12 at 08:37
  • @user539484 DCC is a single-pass compiler; 'forgets .. at the end of module' just cannot be applied to a multi-pass compiler, still if (`#define smth`) is not included (directly or indirectly) into compile module then `smth` should be undefined while compiling module. – kludg Jan 20 '12 at 08:53
  • @Serg, still do not believe? Read the [fine manual](http://docwiki.embarcadero.com/RADStudio/en/DEFINE_directive_%28Delphi%29) then. – OnTheFly Jan 20 '12 at 08:59
  • @user539484, the `C` compiler forgets all defined symbols when it finishes compiling a `.C` file, just as Delphi's compiler forgets all defined symbols when it finishes compiling a `.pas` file. Just like the `C` compiler, `{$DEFINE}`-ed symbols survive inclusion of other files. In fact Delphi's `{$INCLUDE}` directive is mostly used to include files full of `{$DEFINE}` directives. There's not much difference between the compilers regarding this begaviour. User539484, I'm not sure what point you're trying to make. – Cosmin Prund Jan 20 '12 at 09:00
  • @CosminPrund, incorrect. C **compiler** has nothing to do with **preprocessor** macros and directives. – OnTheFly Jan 20 '12 at 09:04
  • @user539484, you're splitting hairs now. I am aware that conceptually the C preprocessor and the compiler are different beasts; Yet all modern compilers do both jobs, probably because there's no way to provide meaningful error messages if they didn't. (you know, if the precompiler was really a separate step, your 5 lines `.C` file including `stdlib.h` might show an error on line 53245) – Cosmin Prund Jan 20 '12 at 09:09
  • @CosminPrund, there is no rocket science in computing physical line number given a flattened file which logically #includes another file. Pascal uses have different semantics. – OnTheFly Jan 20 '12 at 09:32
  • Cosmin: To communicate linenumber transformations between a separate preprocessor and the actual compiler, there is a #line directive. GCC actually uses an external preprocessor. – Marco van de Voort Jan 20 '12 at 10:06
  • Serg: The skipping of stuff after end. has nothing to do with single pass (or do you mean single issue?) or not. That is to be turbo pascal compatible. The fact that preprocessor state doesn't flow from module to module is a deliberate choice, and has nothing to do with the number of passes. – Marco van de Voort Jan 20 '12 at 10:22
3

This pattern is NOT "common in C" code (.c or .cpp source files). It's common in C/C++ headers (.h) files:

#ifndef SOMETHING
#define SOMETHING
#endif

The reason is to prevent the SAME header from being inadvertantly including MULTIPLE TIMES in the same translation unit.

For example, let's say module "a.c" uses header "b.h". And "b.h" #include's "c.h". That means "a.c" explicitly uses "b", and implicitly also uses "c". So far, so good.

Now let's say that "c.h" uses "b.h". The "#ifndef/#define/#endif" stuff PREVENTS "b" from being #include'd a SECOND TIME (once in "a" by "a", the second time in "a" from "c").

This is all unnecessary in Delphi. Delphi "$ifdef" is used only for condition compilation; Delphi "units" take care of potentially recursive and/or circular dependencies.

paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • 2
    What's in a header if it's not code? To me, a C header is still C code. – dreamlax Jan 20 '12 at 07:10
  • Don't quibble about the word "code". The important point is the distinction between "interface" (.h) and "implementation" .cpp". C/C++ headers use the #ifndef preprocessor convention to mitigate recursive/circular definitions. Because of the way Pascal/Delphi uses units, this use of the preprocessor isn't necessary. – paulsm4 Jan 20 '12 at 20:56
  • PS: One of the really nice things about Pascal/Delphi (along with Delphi, and other languages) is the clean, explicit separation of "interface" vs "implementation". – paulsm4 Jan 20 '12 at 20:57
  • I'm not quibbling, there is a distinction between interface and implementation, but both of these fall under "code" in my opinion. – dreamlax Jan 21 '12 at 00:18