Nested C Preprocessor Macros

The C preprocessor and macros are one of the most useful elements for writing portable C/C++ code . It even can  be used to completely different purposes, like text processing.  However, it suffers one major drawback: it does not support nested macros expansion (put differently, macros within macros).

So for example, the following would not work as you expect:

#include <stdio.h>

#define OUTER_MACRO(a,b) (a ## b)
#define INNER_MACRO(a) (#a)

const char *str1 = "hello world";

void main (void)
  // Displays "Hello world"
  printf ("Output is: %s\n", OUTER_MACRO(st,r1)); 

  // Displays "r1"
  printf ("%s", INNER_MACRO (r1)); 
  // .. but this one causes compiler error
  printf ("Output is: %s", OUTER_MACRO (st, INNER_MACRO (r1))); 

Both OUTER_MACRO and INNER_MACRO work as expected, but not in composition. There are however situations where this is desirable.

For example, in the following code:

*fp = fopen (ACCEL_DATA_FILE, "r");

ACCEL_DATA_FILE refers to, as the name suggest, some acceleration data gathered by an instrumentation. As we are interested that the file name for these “Acceleration data” reflect also some date/time, we need to find a way to once open accel_data_20171013.dat and later accel_data_1027104.dat. However, our client code should not change.This can be achieved with a smart macro toolbox, as follows.

First, we define some utility macros (tools_macros.h)

#ifndef __BIZARRE_MACROS__
#define __BIZARRE_MACROS__

#define STRINGIFY2(X) #X

#define CAT(X,Y) CAT2(X,Y)
#define CAT2(X,Y) X##Y



The whole secret here is to do a “double expansion”, so that, contrary to the small test program above, each level of expansion produces something meaningful.

Now we can write

#include "tools_macros.h"

#define REL_PATH .\\data
#define ACCEL_DATA_FILE STRINGIFY(CAT(REL_PATH,\\session_accel.c))

the outer macro would expand as

“CAT(REL_PATH, \\session_accel.c)”

so our line fopen would then include one single macro which expands correctly :). Following the same principle, we can also write:

const int16_t a_test_data[] = {



where the inner generated file contains only data lines


Those lines are the typical output of some instrumentation (X,Y,Z accelerations). Using these tricky macros once again allowed us to reach the code / data isolation paradigm.

Leave a Reply

Your email address will not be published.