X-Macros in C - GeeksforGeeks

16 Apr, 2019
X-Macros are based on the property of nested macros and the ability to define macros inside other macros. X-Macros are very powerful pre-processor technique in the sense that it can create a self-maintaining and inter-dependent piece of code. When the change of one part of a program leads to a change in another part, then the code is said to be inter-dependent.
Syntax:An X macro application consists of two parts:
The definition of the list’s elements:#define VARIABLES \
    X(value1, 1)  \
    .
    .
    .  \
    X(valueN, N)

#define VARIABLES \
    X(value1, 1)  \
    .
    .
    .  \
    X(valueN, N)

Expansion(s) of the list to generate fragments of declarations or statements:#define X(name) int name;
    VARIABLES
#undef X

#define X(name) int name;
    VARIABLES
#undef X

The list is defined by a macro or header file (named, VARIABLES) which generates no code by itself, but merely consists of a sequence of invocations of a macro (classically named “X”) with the elements’ data. Each expansion of VARIABLES is preceded by a definition of X with the syntax for a list element. The invocation of VARIABLES expands X for each element in the list.
Implementations:
Example 1: Following Code explains the working of X-Macros:#include <stdio.h>  // Defines four variables.#define VARIABLES \    X(value1, 1)  \    X(value2, 2)  \    X(value3, 3)  \    X(value4, 4)  // driver program.int main(void){    // Declaration of every variable    // is done through macro.    #define X(value, a) char value[10];        VARIABLES    #undef X          // String values are accepted    // for all variables.    #define X(value, a) scanf("\n%s", value);        VARIABLES    #undef X          // Values are printed.    #define X(value, a) printf("%d) %s\n", a, value);        VARIABLES    #undef X    return 0;}Output:1) geeks
2) for
3) geeks
4) geeksforgeeks
In the above code addition of one or more variables in the Macro “VARIABLES” will lead to its automatic declaration, scanning as well as printing. This simple example clears the working and power of X-Macros. After expansion the above code will look like the code below:#include <stdio.h>  int main(void){    char value1[10];    char value2[10];    char value3[10];    char value4[10];      scanf("\n%s", value1);    scanf("\n%s", value2);    scanf("\n%s", value3);    scanf("\n%s", value4);      printf("%d) %s\n", 1, value1);    printf("%d) %s\n", 2, value2);    printf("%d) %s\n", 3, value3);    printf("%d) %s\n", 4, value4);    return 0;}Output:1) geeks
2) for
3) geeks
4) geeksforgeeks
Although the above code simply explains the power of X-Macros, such use is rare and not recommended because it makes the code less readable. A more practical example is with enum or jump tables.
#include <stdio.h>  // Defines four variables.#define VARIABLES \    X(value1, 1)  \    X(value2, 2)  \    X(value3, 3)  \    X(value4, 4)  // driver program.int main(void){    // Declaration of every variable    // is done through macro.    #define X(value, a) char value[10];        VARIABLES    #undef X          // String values are accepted    // for all variables.    #define X(value, a) scanf("\n%s", value);        VARIABLES    #undef X          // Values are printed.    #define X(value, a) printf("%d) %s\n", a, value);        VARIABLES    #undef X    return 0;}
1) geeks
2) for
3) geeks
4) geeksforgeeks

In the above code addition of one or more variables in the Macro “VARIABLES” will lead to its automatic declaration, scanning as well as printing. This simple example clears the working and power of X-Macros. After expansion the above code will look like the code below:
#include <stdio.h>  int main(void){    char value1[10];    char value2[10];    char value3[10];    char value4[10];      scanf("\n%s", value1);    scanf("\n%s", value2);    scanf("\n%s", value3);    scanf("\n%s", value4);      printf("%d) %s\n", 1, value1);    printf("%d) %s\n", 2, value2);    printf("%d) %s\n", 3, value3);    printf("%d) %s\n", 4, value4);    return 0;}
1) geeks
2) for
3) geeks
4) geeksforgeeks

Although the above code simply explains the power of X-Macros, such use is rare and not recommended because it makes the code less readable. A more practical example is with enum or jump tables.
Example 2: Following code explains the working of X-Macros with enum:#include <stdio.h>  // Defining a macro // with the values of colors.#define COLORS \    X(RED)     \    X(BLACK)   \    X(WHITE)   \    X(BLUE)  // Creating an enum of colors// by macro expansion.enum colors {    #define X(value) value,        COLORS    #undef X};  // A utility that takes the enum value// and returns corresponding string valuechar* toString(enum colors value){    switch (value) {        #define X(color) \            case color:  \                return #color;                COLORS        #undef X    }}  // driver program.int main(void){    enum colors color = WHITE;    printf("%s", toString(color));    return 0;}Output:WHITE
In the above code, any addition or removal of any constant from the COLORS macro will automatically reflect in the definition of enum as well as toString() function. Which is why X-Macros are used to produce self-maintaining codes. After macro expansion above code will look like the code below:#include <stdio.h>  // Creating an enum of colors.enum colors {    RED,    BLACK,    WHITE,    BLUE};  /*A utility that takes the enum value and returns corresponding string value*/char* toString(enum colors value){    switch (value) {    case RED:        return "RED";    case BLACK:        return "BLACK";    case WHITE:        return "WHITE";    case BLUE:        return "BLUE";    }}  // driver program.int main(void){    enum colors color = WHITE;    printf("%s", toString(color));    return 0;}Output:WHITE

#include <stdio.h>  // Defining a macro // with the values of colors.#define COLORS \    X(RED)     \    X(BLACK)   \    X(WHITE)   \    X(BLUE)  // Creating an enum of colors// by macro expansion.enum colors {    #define X(value) value,        COLORS    #undef X};  // A utility that takes the enum value// and returns corresponding string valuechar* toString(enum colors value){    switch (value) {        #define X(color) \            case color:  \                return #color;                COLORS        #undef X    }}  // driver program.int main(void){    enum colors color = WHITE;    printf("%s", toString(color));    return 0;}
WHITE

In the above code, any addition or removal of any constant from the COLORS macro will automatically reflect in the definition of enum as well as toString() function. Which is why X-Macros are used to produce self-maintaining codes. After macro expansion above code will look like the code below:
#include <stdio.h>  // Creating an enum of colors.enum colors {    RED,    BLACK,    WHITE,    BLUE};  /*A utility that takes the enum value and returns corresponding string value*/char* toString(enum colors value){    switch (value) {    case RED:        return "RED";    case BLACK:        return "BLACK";    case WHITE:        return "WHITE";    case BLUE:        return "BLUE";    }}  // driver program.int main(void){    enum colors color = WHITE;    printf("%s", toString(color));    return 0;}
WHITE

Advantages of X-Macros
X-Macros are extensively used in Operating System development by creating separate header files for maintainability as well as readability
Helps to maintain complex programming easily
it can create a self-maintaining and inter-dependent piece of code
Disadvantages of X-Macros
The code becomes less readable
Code is complex to understand
Commonly used for internal programming only, like OS programming.
Reference: https://en.wikipedia.org/wiki/X_Macro
C Basics
C Macro
C Language
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Multidimensional Arrays in C / C++
Left Shift and Right Shift Operators in C/C++
Function Pointer in C
Substring in C++
Core Dump (Segmentation fault) in C/C++
rand() and srand() in C/C++
fork() in C
std::string class in C++
Converting Strings to Numbers in C/C++
Enumeration (or enum) in C