0

I tried to wrap my head around it but gave up. Can you please explain the following, taken from the C book by Mike Banahan (Section 7.3.7 Conditional compilation). Despite multiple efforts, I can't grasp the part after "The token sequence that makes up....". Can you please state in simple terms

1) What exactly is the "defined" prefix thing, and

2) What with names and even C keywords reducing to zero (Not to forget the confusing reference to sizeof)?

Explanation with a little piece of code will be very much helpful. Thank you.

The #if and #elif constructs take a single integral constant expression as their arguments. Preprocessor integral constant expressions are the same as other integral constant expressions except that they must not contain cast operators. The token sequence that makes up the constant expression undergoes macro replacement, except that names prefixed by defined are not expanded. In this context, the expression defined NAME or defined ( NAME ) evaluates to 1 if NAME is currently defined, 0 if it is not. Any other identifiers in the expression including those that are C keywords are replaced with the value 0. Then the expression is evaluated. The replacement even of keywords means that sizeof can't be used in these expressions to get the result that you would normally expect.

Thokchom
  • 1,602
  • 3
  • 17
  • 32
  • 1
    `#if defined NAME` - https://gcc.gnu.org/onlinedocs/cpp/Defined.html – Karoly Horvath Sep 28 '14 at 18:10
  • @KarolyHorvath Well, getting a grip now...but what about names, including C keywords, being replaced by 0? Can you give a small code sample? And what exactly does the last line about sizeof mean? – Thokchom Sep 28 '14 at 18:13

2 Answers2

4

This refers to the enabling or disabling of conditional compilation. It discusses both using integer expressions and preprocessor macros.

Let's say you want some code enabled only when you are debugging. You can do something like this:

#define DEBUGGING 1

#if DEBUGGING
    printf( "I'm debugging\n" );
#endif

void function( void ) {
   if ( DEBUGGING ) {
       printf( "I'm debugging\n" );
   }
}

This code will work, i.e. you will get two printed output statements that say, "I'm debugging."

The reason is because in the first block (#if DEBUGGING), the macro DEBUGGING is converted to a 1 (the value it was defined to represent), then it is evaluated as an integer expression. In other words, the preprocessor reduces the code to this:

#if 1
    printf( "I'm debugging\n" );
#endif

void function( void ) {
   if ( 1 ) {
       printf( "I'm debugging\n" );
   }
}

But now consider the case where the #DEBUGGING macro is not defined at all.

In that case, the preprocessor will reduce the code to this:

//#define DEBUGGING 1   <-- commented out

#if 0  // <--- DEBUGGING is not a macro-word here, so it is converted to 0
    printf( "I'm debugging\n" );
#endif

void function( void ) {
   if ( DEBUGGING ) { // <---- look what happened here! *Not* in "#if", so NOT preprocessed to zero!
       printf( "I'm debugging\n" );
   }
}

Because of the way the C language is defined, the #if test will reduce any word it doesn't recognize as a macro (e.g. int, sizeof, myVariableName, SOME_OTHER_UNDEFINED_THING) to 0, and no error will be given. The first printf statement just won't be compiled in.

However, the second statement which is using what was a macro in the previous version of the code will NOT get a replacement since there is no longer a DEBUGGING macro defined. In this case, after the preprocessor run, the compiler will complain about a name it doesn't recognize since it will literally see the word DEBUGGING and not know what to make of it (it isn't a variable name or keyword, etc.).

That's the first thing the paragraph you quoted says.

The second thing it mentions is that #if statements will reduce its arguments to the result of a single integer expression. That means you can compile code that looks like this:

#define VERSION 2

#if VERSION > 1
    printf( "this code only runs on v2+\n" );
#endif

The preprocessor first replaces the word VERSION with the thing it represents:

#if 2 > 1
...

then the preprocessor evaluates the line as an integer expression. Since 2 is greater than 1 (true), the expression/line reduces to:

#if 1
    printf( "this code only runs on v2+\n" );
#endif

Regarding sizeof (and any other keyword or non-macro word), in #if statements, all names the preprocessor doesn't recognize are converted to zero. This is so it can handle conditional compilation on macros that aren't there, as shown above when I commented out DEBUGGING.

So this code, which looks like something you might want to do:

#if sizeof(int) > 2
    printf( "This machine has 4+ byte integers\n" );
#endif

Will reduce to this:

#if 0(0) > 2  // sizeof and int are non-macro-words so they get replaced with 0

This is an invalid expression, so the compiler will report an error. Thus you cannot use sizeof (or any other keyword or variable name) in a preprocessor macro.

If you think about it by separating the C preprocessor from the C compiler this makes sense. The preprocessor doesn't know about regular C keywords, that's not its job. It just parses the tokens it cares about (#if, etc.) then hands off the post-processed C file to the compiler.

par
  • 17,361
  • 4
  • 65
  • 80
2

'defined' here refers to the preprocessor directive #define.

A section of source code may include a #define as follow:

#define UNITTEST

Then later, you can write

#ifdef UNITTEST
  runtest();
#endif

There are at least two ways to define a symbol for the preprocessor. The first is to include a #define in a file, as above. The second is to include -D<symbol> directives from the gcc command line, as in -DUNITTEST. In this case the symbol UNITTEST will be defined throughout the compilation.

It is good practice, in general, to not #define a symbol in a source file without a subsequent #undef to remove the symbol. Without a closing #undef, the symbol will remain defined and the project will be hard to debug (or worse, there can be name collisions). So, for instance, within a source file,

#define ABBV 
...
#undef ABBV

When running unit tests, then instruct the build system (make, SCons) to apply the -D directive and uniformly code the source files to branch on this symbol.

Note that #define can define symbols in the manner of the text you quoted (UNITTEST resolves to 1 if defined) or it can define a marco with argument. The canonical macro being

#define SQR(x) ((x) * (x))
int a(1);
int b(SQR(a));

It remains good practice to #undef a macro after its use, or put 'global' macros in their own file for easy identification. So, given that I wrote SQR above, I'd follow with

#undef SQR

Referring to your specific question about conditional compilation, I use -D directives (often many) on the compiler commandline when building tests, dev or production. Then I test for these symbols in the source code. And, for #defines in source code, I define either local macros (eg SQR macro) and undefine them, or write global macros into a single core_macros.h file.

JayInNyc
  • 2,193
  • 3
  • 20
  • 21
  • When `DEBUG` is not defined the preprocessor will generate `if (DEBUG)` *not* `if (0)`. This is why you get an error in the `if` test instead of it failing silently. – par Sep 28 '14 at 18:50
  • how about I scratch that part of my answer. Actually I never do that so I should have left it at that. – JayInNyc Sep 28 '14 at 18:51
  • @JayInNyc +1. Thanks for setting the ball rolling for my "not-so-cool-to-look-at" question :-) ! – Thokchom Sep 28 '14 at 19:06
  • @Thokchom glad it helped. @par and I gave different views of the answer. @par answered your question spot on, mine is more about best practices when using `#defines`. Cheers – JayInNyc Sep 28 '14 at 19:17