Question

From How To Write Unmaintainable Code (indentation mine for structure):

One of the most imaginative uses of the preprocessor I have heard of was requiring five passes through CPP before the code was ready to compile. Through clever use of defines and ifdefs, a master of obfuscation can make header files declare different things depending on how many times they are included. This becomes especially interesting when one header is included in another header. Here is a particularly devious example:

#ifndef DONE
    #ifdef TWICE
        // put stuff here to declare 3rd time around
        void g(char* str);
        #define DONE
    #else // TWICE
        #ifdef ONCE
            // put stuff here to declare 2nd time around
            void g(void* str);
            #define TWICE
        #else // ONCE
            // put stuff here to declare 1st time around
            void g(std::string str);
            #define ONCE
        #endif // ONCE
    #endif // TWICE
#endif // DONE

Given this code, I would expect this behavior:

  1. evaluate #ifndef DONE as true, entering that block
  2. evaluate #ifdef TWICE as false
  3. branch to and enter #else // TWICE
  4. evaluate #ifdef ONCE as false
  5. branch to and enter #else // ONCE
  6. #define ONCE
  7. #endif three times

Why does the preprocessor make multiple "passes"? Do if-elses in preprocessor directives not behave like standard if-else control structures? Does calling a function inside a preprocessor if-statement cause complete reevaluation?

Was it helpful?

Solution

Quoting from the thing you quoted:

a master of obfuscation can make header files declare different things depending on how many times they are included

The author here is assuming you include this file multiple times. In other words, you might have a file that looks something like this:

#include "that.file.in.your.question.h"
#include "that.file.in.your.question.h"
#include "that.file.in.your.question.h"

The first time, the flow is as you describe, causing ONCE to be defined. The second time, because ONCE is now defined, it takes a different branch (the one where TWICE is defined). The third time, because TWICE is defined, it takes yet a different branch (the one where DONE is defined). Finally, the fourth time, and any time thereafter, because DONE is defined, the whole thing is skipped. That is unless of course some other preprocessor directive undefines DONE.

OTHER TIPS

The preprocessor does do all its job in one pass.

What Roedy Green means is that this header file will perform differently if included more than once into the same compilation unit. Say, the file is named header.h, then, if a compilation unit invokes the file four times, each invocation will produce different results:

// Here, nothing is defined
#include "header.h"
// Here, ONCE is defined
#include "header.h"
// Here, TWICE is defined
#include "header.h"
// Here, DONE is defined
#include "header.h"

will expand into:

void g(std::string str); // first invocation
void g(void* str);       // second invocation
void g(char* str);       // third invocation
                         // fourth invocation

This is a device which may come handy on some occasions, though if not documented properly it also obfuscates the programmer's intent to some extent.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top