CS253: Software Development with C++

Spring 2021

Conditional Compilation

Show Lecture.ConditionalCompilation as a slide show.

CS253 Conditional Compilation

Buridan’s Ass

The Preprocessor

Preprocessor Directives: not just for #include guards!

#if conditioncompile-time decision (compile the code or don’t)
#ifdef symbolwas this compile-time symbol defined?
#ifndef symbolwas this compile-time symbol not defined?
#elseoptional other half of #if or #ifdef
#elif conditionanother compile-time decision
#endifone for each #if or #ifdef
#errorpreprocessor-time errors
#pragmaimplementation-defined

Basic structure

cout << "alpha\n";
#if 2 > 3
    cout << "beta\n";
#elif 4 != 5
    cout << "gamma\n";
#endif
alpha
gamma

Comments

You can also “comment out” sections of code:

cout << "red\n";
#if 0
    this doesn’t work yet
    cout << "white\n";
#endif
cout << "blue\n";
red
blue

Comments

char *allocate(string /* type */) {
    return new char[256];  // Just return maximum buffer for now
}

int main() {
    cout << "Hello\n";
}
Hello

Conditional Debugging

You can use a flag to control conditional compilation:

const auto who = getenv("USER");
const auto pid = getpid();
#if DEBUG
    cout << "who is " << who << '\n';
    cout << "pid is " << pid << '\n';
#endif
string filename = "/tmp/temp-"s + who + '-' + to_string(pid);
cout << "filename: " << filename << '\n';
filename: /tmp/temp-cs253-3811309

The flag DEBUG isn’t defined, so the debug information isn’t printed.

Conditional Debugging

Let’s enable the flag using #define:

#define DEBUG 1
const auto who = getenv("USER");
const auto pid = getpid();
#if DEBUG
    cout << "who is " << who << '\n';
    cout << "pid is " << pid << '\n';
#endif
string filename = "/tmp/temp-"s + who + '-' + to_string(pid);
cout << "filename: " << filename << '\n';
who is cs253
pid is 3811310
filename: /tmp/temp-cs253-3811310

You can also turn the flag on via: g++ -D DEBUG=1 or g++ -D DEBUG or g++ -DDEBUG which are the same as: #define DEBUG 1

Compiler Version

Most compilers provide ways to check the compiler version at compile-time. The gcc/g++ compiler provides several symbols:

cout << "Major   " << __GNUC__ << '\n'
     << "Minor   " << __GNUC_MINOR__ << '\n'
     << "Patch   " << __GNUC_PATCHLEVEL__ << '\n'
     << "Version " << __VERSION__ << '\n';
Major   8
Minor   3
Patch   1
Version 8.3.1 20191121 (Red Hat 8.3.1-5)

cout << __GNUC__ << '.'
     << __GNUC_MINOR__ << '.'
     << __GNUC_PATCHLEVEL__ << '\n';
8.3.1

Compiler Version

You can use the symbols to decide whether a non-standard feature is available:

#if __GNUC__ >= 50
    // Only available on g++ starting with version 50
    string filename = make_temp_file();
#else
    // Do it the hard way:
    const auto who = getenv("USER");
    const auto pid = getpid();
    string filename = "/tmp/temp-"s + who + '-' + to_string(pid);
#endif
cout << "filename: " << filename << '\n';
filename: /tmp/temp-cs253-3811313

Compiler Version

We can’t do the former test using if:

string filename;
if (__GNUC__ >= 50)
    // Only available on g++ starting with version 50
    filename = make_temp_file();
else {
    // Do it the hard way:
    const auto who = getenv("USER");
    const auto pid = getpid();
    filename = "/tmp/temp-"s + who + '-' + to_string(pid);
}
cout << "filename: " << filename << '\n';
c.cc:4: error: 'make_temp_file' was not declared in this scope

Both parts of the normal if get compiled, but make_temp_file() isn’t defined on this compiler. Boom!

C++ Standard Version

For standard features, use __cplusplus, the YYYYMM of the standard in effect. This uses vector::shrink_to_fit() when using C++11 or later; better than guessing based on the compiler version:

vector<long double> v;
for (int i=1; i<=10000; i++)
    v.push_back(i*i);
#if __cplusplus >= 201103L
    const auto old = v.capacity();
    v.shrink_to_fit();   // added in C++11
    cout << old << " ➔️ " << v.capacity();
#endif
16384 ➔️ 10000
Why the L in 201103L?

__cplusplus is defined to be a long literal; this evades fussy compiler warnings about long >= int comparison.

System Detection

Every compiler defines symbols that allow you to detect the compiler or architecture:

See https://sourceforge.net/p/predef/wiki/OperatingSystems/

System-Dependent Code

#if __APPLE__
    cout << "On an Apple product\n";
#elif __linux__
    cout << "On Linux\n";
#elif __hpux
    cout << "On HP-UX\n";
#else
    #error What is this, a Turing Machine!?
#endif
On Linux

Example

There is no standard solution to this problem.

Partial Solution

Most compilers have system-dependent ways of solving this problem. The real problem is knowing which one to use. The Linux solution:

cout << "I am " << program_invocation_name;
I am ./a.out

HP-UX solution:

extern const char **__argv_value;
cout << "I am " << __argv_value[0];
/tmp/cc2H0Vb6.o: In function `main':
c.cc:(.text+0x19): undefined reference to `__argv_value'
collect2: error: ld returned 1 exit status

Naturally, the HP-UX solution doesn’t work on our Linux webserver. ☹

$ uname -o
GNU/Linux

Real solution

Of course, the real solution uses conditional compilation!

#if __linux__
    string pname() { return program_invocation_name; }
#elif __hpux
    string pname() {
        extern const char **__argv_value;
        return __argv_value[0];
    }
#else
    #error Good luck with your stone knives and bearskins.
#endif

int main() {
    cout << pname() << '\n';
}
./a.out

Hide the OS-specific code in the definition of pname(). The rest of the program is clean, beautiful, and DRY.

False Solution

The test must occur at compile time—can’t define a function twice!

if (__linux__) {
    string pname() { return program_invocation_name; }
}
else if (__hpux) {
    extern const char **__argv_value;
    string pname() { return __argv_value[0]; }
}
else {
    // I can’t deal with this system!
}

int main() {
    cout << pname() << '\n';
}
c.cc:1: error: expected unqualified-id before 'if'