Skip to content

Last updated:2026-10-12

C++ Macros and Conditional Compilation

About the Author:

Personal WeChat official account:

wechat

1. Special Symbols in Macros

They come in several kinds: #, ##, \

1.1 The Stringize Operator (#)

When a # precedes a parameter inside a macro, the preprocessor converts that parameter into a character array. Put another way: # means "stringize"; a # appearing in a macro definition converts the parameter that follows it into a string.

Note: it can only be used in macros that take parameters, and it must be placed before the parameter name in the macro body.

For example:

#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s 
int main() {
    exp("hello");
    exp1(hello);

    string str = exp2(   bac );
    cout<<str<<" "<<str.size()<<endl;
    /**
     * 忽略传入参数名前面和后面的空格。
     */
    string str1 = exp2( asda  bac );
    /**
     * 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
     * 用每个子字符串之间以一个空格连接,忽略剩余空格。
     */
    cout<<str1<<" "<<str1.size()<<endl;
    return 0;
}

The code above shows the basic usage together with the whitespace handling rules, which are as follows:

  • Whitespace before and after the argument name passed in is ignored.
string str = exp2(   bac );
cout<<str<<" "<<str.size()<<endl;

Output:

bac 3
  • When there is whitespace between the words of the argument passed in, the compiler automatically joins the sub-strings, connecting each sub-string with a single space and ignoring the remaining whitespace.
string str1 = exp2( asda  bac );
cout<<str1<<" "<<str1.size()<<endl;

Output:

asda bac 8

1.2 The Token-Pasting Operator (##)

"##" is a separation-and-connection mechanism: it first separates, then forcibly joins. It turns multiple formal parameters of a macro definition into a single actual parameter name.

Points to note:

(1) When ## joins formal parameters, the whitespace before and after ## is optional.

(2) The actual parameter name produced by the join must be a name that actually exists, or a macro definition the compiler already knows.

(3) If the parameter after ## is itself a macro, ## prevents that macro from being expanded.

Example:

#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s)  //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_  ##  s)  //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
    // 注意事项1
    const char * gc_hello = "I am gc_hello";
    expA(hello);
    expB(hello1);
}

1.3 The Line-Continuation Operator ()

When a macro definition cannot be expressed completely on a single line, you can use "\" to indicate that the macro definition continues on the next line.

Note: leave a space before the .

#define MAX(a,b) ((a)>(b) ? (a) \
   :(b))  
int main() {
    int max_val = MAX(3,6);
    cout<<max_val<<endl;
}

For the code above see: sig_examp.cpp

2. Uses of do{...}while(0)

2.1 Avoiding Misinterpretation

For example:

#define fun() f1();f2();
if(a>0)
    fun()

Once this macro is expanded, it becomes:

if(a>0)
    f1();
    f2();

The intent is that f1 and f2 execute when a>0, but in reality f2 executes every time, so this is wrong.

To solve this kind of problem, you can usually adopt a {} block when writing the code.

For example:

#define fun() {f1();f2();}
if(a>0)
    fun();
// 宏展开
if(a>0)
{
    f1();
    f2();
};

But you will notice that after expansion the macro above carries an extra semicolon, which is not quite right syntactically. (Although it compiles and runs without problems, it would normally be without the semicolon.)

2.2 Avoiding goto Control Flow

In some functions we may need to do cleanup work before the return statement — for example, releasing memory allocated by malloc at the start of the function. Using goto is always a simple way to do this:

int f() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
#ifndef DEBUG
    int error=1;
#endif
    if(error)
        goto END;
    // dosomething
END:
    cout<<"free"<<endl;
    free(p);
    return 0;
}

But since goto does not fit the structured approach of software engineering and can make code hard to understand, many people advocate against using it. In that case, we can use do{...}while(0) to do the same thing:

int ff() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
    do{ 
#ifndef DEBUG
        int error=1;
#endif
        if(error)
            break;
        //dosomething
    }while(0);
    cout<<"free"<<endl;
    free(p);
    return 0;
}

Here the body of the function is wrapped in do{...}while(0), with break replacing goto, and the cleanup work comes after the while. Now we achieve the same effect, and the code is far more readable and maintainable than the goto version above.

2.3 Avoiding Warnings Caused by Macros

Because of the constraints of different architectures, empty macros are used a lot in the kernel. At compile time these empty macros produce warnings. To avoid such warnings, we can define empty macros with do{...}while(0):

#define EMPTYMICRO do{}while(0)

2.4 Defining a Single Function Block for Complex Operations

If you have a complex function with many variables and you do not want to add a new function, you can use do{...}while(0) and write your code inside it. Inside, you can define variables without worrying that their names clash with variables that appear before or after the block in the function.

This refers to the situation where one variable is used in several places (with a different meaning in each): we can narrow its scope inside each do-while, for example:

int fc()
{
    int k1 = 10;
    cout<<k1<<endl;
    do{
        int k1 = 100;
        cout<<k1<<endl;
    }while(0);
    cout<<k1<<endl;
}

For the code above see: do_while.cpp

Further reading: https://www.cnblogs.com/lizhenghn/p/3674430.html

Comments