Conditional preprocessor directives in C/C++

Conditional preprocessor directives in C/C++

When you are facing to use microcontroller vendor's library code or toolchain, you would see many conditional preprocessor directives. In using conditional preprocessor directives, you would be able to write code that can be generalized. For example, some assembly languages are compiler specific so you can pick any supported compiler with one general code with conditional preprocessor directives.

Here is the list of conditional preprocessor directives in C/C++

  • #ifdef

  • #ifndef

  • #if

  • #else

  • #elif

  • #endif

Those conditional preprocessor directives cannot be used alone. #ifdef, #ifndef and #if are used as start. #endif is used as an end. I will introduce a combination of those with examples.

#ifdef DEBUG
int print(void)
{
    printf("debug build\r\n");
    return 0;
}
#else
int print(void)
{
    printf("production build\r\n");
    return 1;
}
#endif

There are two print function definitions, however, during run-time, only one of them will be compiled. If the symbol DEBUG is defined somewhere, the print function would return 0, otherwise, return 1.

When you want to use a logical operator with a conditional preprocessor, you could use #if with defined.

#if defined(DEBUG) && defined(EMC)
int print2(void)
{
    printf("debug build2\r\n");
    return 2;
}
#endif

Sometimes we need to provide special firmware for EMC testing. Let's say I would like to separate debug version of EMC and the production version of EMC. #if with #defined can achieve this need. The code above can achieve debug version of EMC.

The next example demonstrates if, else if conditions.

#if !defined(DEBUG)
int print3(void)
{
    return 3;
}
#elif defined(EMC)
int print3(void)
{
    return 4;
}
#endif

If a symbol DEBUG is not defined, the first print3 will be compiled and returned 3. If a symbol DEBUG is defined and a symbol EMC is defined, the second print3 will be combined and returned 4.

The last example introduces #ifndef.

#ifndef ABC
int print4(void)
{
    return 5;
}
#endif

print4 definition is only valid when a symbol ABC is not defined.

All conditional preprocessor directives are introduced with various examples. They are useful in terms of generic library code. However, when the directives are used everywhere, the code will be unreadable and hard to debug the code. This preprocessor shall be carefully used.

Ceedling project code can be found here:

//pre_processor.c
#include "pre_processor.h"
#include <stdlib.h>
#include <stdio.h>

#define DEBUG

#define EMC

#ifdef DEBUG
int print(void)
{
    printf("debug build\r\n");
    return 0;
}
#else
int print(void)
{
    printf("production build\r\n");
    return 1;
}
#endif

#if defined(DEBUG) && defined(EMC)
int print2(void)
{
    printf("debug build2\r\n");
    return 2;
}
#endif

#if !defined(DEBUG)
int print3(void)
{
    return 3;
}
#elif defined(EMC)
int print3(void)
{
    return 4;
}
#endif

#ifndef ABC
int print4(void)
{
    return 5;
}
#endif
//pre_processor.h
#ifndef PRE_PROCESSOR_H
#define PRE_PROCESSOR_H
int print(void);
int print2(void);
int print3(void);
int print4(void);
#endif // PRE_PROCESSOR_H
//test_pre_processor.c
#include "unity.h"

#include "pre_processor.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_pre_processor_print(void)
{
    int result = print();

    TEST_ASSERT_EQUAL(0, result);
}

void test_pre_processor_print2(void)
{
    int result = print2();

    TEST_ASSERT_EQUAL(2, result);
}

void test_pre_processor_print3(void)
{
    int result = print3();

    TEST_ASSERT_EQUAL(4, result);
}

void test_pre_processor_print4(void)
{
    int result = print4();

    TEST_ASSERT_EQUAL(5, result);
}

Did you find this article valuable?

Support Hyunwoo Choi by becoming a sponsor. Any amount is appreciated!