C/C++: Preprocessing (Part 2)

Table of contents

1. Review the compilation and linking process of the program

2. Preprocessing pre-defined #define

1. The identifier defined by #define

2. #define defined macro

3. Expressions with side effects as macro arguments 

4. Two classic macros

5. Summary of some precautions for using #define

6. Comparison of macros and functions

7.#undef

Attachment: Three cold facts about #define

3. Conditional compilation

4. Preprocessing #include

1.#include<> 与  #include" "

2. Duplicate inclusion of header files


1. Review the compilation and linking process of the program

2. Preprocessing pre-defined #define

1. The identifier defined by #define

The identifier defined by #define will be replaced by the defined content in the preprocessing stage of the source code file

for example:

#define MAX 1000
//MAX 是被宏定义的标识符
//MAX空格后的所有内容是其定义的内容           

Notice:

  • Identifiers end with spaces (that is, identifiers defined by #define do not contain spaces)
  • Do not add the #define statement at the end; otherwise the semicolon will be replaced in the code segment and cause some bugs

2. #define defined macro

Identifiers defined by #define can have parameters (similar to functions), and identifiers with parameters defined by #define are called macros

for example:

#define N 4
#define Y(n) ((N+2)*n)


z = 2 * (N + Y(5+1));  //z最后的结果是多少?
  • Y(n) is a macro, Y is the macro name, n is the parameter of the macro
  • The result analysis of z:

Therefore, the final calculated result of z is 70. It can be seen that 5+1 in the formula is not calculated first , so the macro definition used to evaluate the numerical expression must pay attention to enclosing the macro parameters and the macro body in parentheses To avoid the result of the operation not meeting expectations due to operator precedence issues , the more rigorous way of writing the macro Y(n) should be:

#define Y(n) (((N)+2)*(n))

3. Expressions with side effects as macro arguments 

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

So in the end: z=9, x=6, y=10. ( x increments twice, y increments once )

  • It can be seen that an expression with side effects (a side effect is that the value of the related variable is changed when the expression is evaluated ) is very dangerous as a macro argument

4. Two classic macros

  • Baidu engineer written test question: Write a macro to calculate the byte offset of a member variable in the structure relative to the first address ( macro implementation) ( the actual parameter of the macro can be the variable type name )offsetof
#define OFFSETOF(structname,numbername) (size_t)&(((structname *)0)->numbername)

#define OFFSETOF(structname,numbername) (size_t)&(((structname *)0)->numbername)

//宏的测试
typedef struct Node
{
    int i;
    char c;
    short d;
}Node;
int main ()
{
    printf("%u\n",OFFSETOF(Node,i));
    printf("%u\n",OFFSETOF(Node,c));
    printf("%u\n",OFFSETOF(Node,d));
    return 0;
}

This is a typical use of type names as macro parameters.

For structure member offsets, refer to structure memory alignment:  http://t.csdn.cn/Vd6ix

  • A classic algorithm macro: Write a macro that can swap the odd and even bits of the two's complement of a positive integer (32 bits) 

for example:

#define EXCHANGEBIN(NUM) (((NUM)&(0x55555555))<<1)|(((NUM)&(0xaaaaaaaa))>>1)

Algorithm analysis:

#include <stdio.h>

#define EXCHANGEBIN(NUM) (((NUM)&(0x55555555))<<1)|(((NUM)&(0xaaaaaaaa))>>1)


//宏测试

int main()
{
    int num = 426;                      //二进制补码为:0000 0000 0000 0000 0000 0001 1010 1010
    printf("%u\n",EXCHANGEBIN(num));    //转换后补码为:0000 0000 0000 0000 0000 0010 0101 0101
    return 0;
}

 

5. Summary of some precautions for using #define

  • When an expression with side effects (that will change the value of a variable) is used as an actual parameter of a macro, pay attention to the impact of the number of times it appears in the macro body
  • The definition of the macro body should use more parentheses to clearly indicate the associativity of the operation
  • Macro parameters can be any type of variable or even a type name , pay attention to reasonable type matching when using
  • The macro text replacement mechanism will reduce the maintainability of the code (macros replaced in the source text will have unpredictable interactions with the context of the source code ), and more complicated processes should not be encapsulated by macros .
  • The macro body is replaced into the source code text in the precompilation stage. When the code is debugged, the source code segment seen by the user is different from the actually debugged code segment.
  • When the preprocessor searches for symbols defined by #define, the contents of string constants are not searched

    for example:

    #define N 4
    char arr[]="N";
    //arr中的N不会被替换

6. Comparison of macros and functions


#define EXCHANGEBIN(NUM) (((NUM)&(0x55555555))<<1)|(((NUM)&(0xaaaaaaaa))>>1)

int ExchangeBin(int num)
{
    return (((num)&(0x55555555))<<1)|(((num)&(0xaaaaaaaa))>>1);
}

int main()
{
    int num = 426;                  
    EXCHANGEBIN(num);  
    ExchangeBin(num);

    return 0;
}

The macros and functions in the code segment achieve the same function, but actually the assembly instructions for executing the macro and executing the function are quite different.

Execute the assembly instruction of the EXCHANGEBIN(num) macro:

Assembly instructions to execute the ExchangeBin(num) function:

  • It can be seen that to achieve the same function, the instruction segment for executing macros is much more concise than the instruction segment for executing functions. Therefore, for some simple expressions that are frequently used in the program , using macros to encapsulate them will make the program run more efficiently. higher .
  • Because macros are text replacements, after preprocessing, macros may greatly increase the text length of the source code , making the program take up more memory when running, but functions will not have such problems, because the function body of a function is stored in the memory Only one copy will be stored in the read-only constant area of ​​.
  • Macro parameters have no type restrictions ( even type names ), while function parameters will have strict type checking , and functions are safer at this point
  • The macro body is difficult to debug statement by statement (the expanded macro body cannot be seen in the macro call statement of the source file), but the function can be debugged statement by statement
  • Macros cannot be recursive, functions can be recursive

New syntax features proposed for macros in C++:

After summarizing the advantages and disadvantages of macros, a new compile-time feature is given in C++ syntax : inline function .

Using inline functions instead of macros in C++ can improve the security and maintainability of the code while improving the efficiency of the program (the biggest disadvantage of macros is that there is no type safety check, and the macro body is affected by the context code segment. and poor readability and maintainability ). For C++ inline functions, see: http://t.csdn.cn/cWQPL

7.#undef

The #undef directive is used to remove a macro definition

#undef NAME

Attachment: Three cold facts about #define

  1. Use # to turn a macro parameter into a corresponding string
    int i = 10;
    #define PRINT(FORMAT, VALUE)\            //宏体可以分行定义(用反斜杠加回车将宏体内容换行)
    printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
    
    
    int main()
    {
       PRINT("%d", i+3);  // #VALUE会使参数 i+3 变为对应的字符串"i+3"
    }
    
  2. ## You can combine the symbols on both sides of it into one symbol.
    It allows macro definitions to create identifiers from separate text fragments (identifiers must be pre-defined).
    #define ADD_TO_SUM(num, value) \
    sum##num += value;
    
    
    int main ()
    {
        int sum5 =10;
        ADD_TO_SUM(5, 10); //预处理将该语句替换为 sum5 += 10
    }
    
  3. The command line definition of gcc:
    #include <stdio.h>
    int main()
    {
        int array [ARRAY_SIZE];
        return 0;
    }

    We can specify the value of the constant ARRAY_SIZE on the gcc compilation command line:

    gcc -D ARRAY_SIZE=10 -E ./testproject/test.c -o test.i

3. Conditional compilation

  1. Constant expression conditional compilation:

    //单分支条件编译指令
    #if 常量表达式
    
        //代码段
    
    #endif
    
    //多分支条件编译指令
    #if   常量表达式
       
     //代码段
    
    #elif 常量表达式
        
     //代码段
    
    #else
    
     //代码段
    
    #endif
    
    //#elif 可以类比 else if 来理解

    When the constant expression is true, the code segment between #if and #endif (or #elif, #else) will be compiled, and if it is false, the compiler will automatically block #if and #endif (or #elif, #else ) between code segments ( constant expressions are evaluated by the preprocessor )

  2. Conditional compilation of #define

    ​
    
    #ifdef symbol
    
       //代码段     
        
    #enif
    
    如果symbol被#define定义了,则编译器会编译代码段,如果symbol没有被#define定义,则编译器不会编译代码段
    
    ​
    #ifndef symbol
    
        //代码段    
    
    #endif
    
    如果symbol没有被#define定义则编译器会编译代码段,如果symbol被#define定义了则编译器不会编译代码段
条件编译的嵌套
#ifdef OS_Unix

    #ifdef OPTION1
        unix_version_option1();
    #endif

    #ifdef OPTION2
        unix_version_option2();
    #endif

#elifdef OS_MSDOS

    #ifdef OPTION2
        msdos_version_option2();
    #endif

#endif

 Conditional compilation is common in some projects as well as in language standard library sources.

4. Preprocessing #include

The function of #include is to "copy and paste" the content in the specified header file to the current source file

1.#include<> 与  #include" "

  • Local files contain directives
    #include "filename"

    Compiler search method: The compiler will first search for the filename file in the path where the current source file is located . If the header file is not found, the compiler will search for the filename file in the standard library path . Compile error if not found

  • The library file contains the directive
     

    #include <filename>

    The compiler will go directly to the standard library path to find the filename file , and if it cannot find it, it will prompt a compilation error

Selecting the corresponding file inclusion instructions according to the specific situation can improve the compilation efficiency of the compiler, and it can make it easier to distinguish library files and local files at the source code level.

2. Duplicate inclusion of header files

Duplicate inclusion of header files in the same source file

Such a scenario is easy to appear in a complex project engineering:

In the above scenario, comm.h is repeatedly included twice in test.c , which means that the same content in comm.h will be "copy-pasted" twice into test.c , which may cause program link errors ( This link error often toss people for a long time)

  • Adding the #pragma once directive in the header file can prevent the header file from being repeatedly included in the same source file
    #pragma once

    It is good programming practice to add the #pragma once directive to each header file

The same header file is included in multiple source files

  • A header file is often included by multiple source files at the same time , so the definition of global variables and the definition of the function body should be avoided in the header file , otherwise the definition of the same variable (or function) will appear multiple times in the global domain and cause Link error.
  • The declaration and definition of the global identification name are separated , and the declaration is uniformly placed in the header file, and the definition is uniformly placed in the source file, which is a necessary programming quality

Guess you like

Origin blog.csdn.net/weixin_73470348/article/details/128961818
Recommended