C language basics: the use of preprocessing instructions

This article combines work experience to study the usage of common preprocessing instructions in C language.

1 The concept of preprocessing directives

The first stage in which the compiler compiles C code is preprocessing. The preprocessing stage will process the preprocessing instructions, "translate" the C code into another form, and prepare for the subsequent compilation, assembly, and linking processes. Each of the following sections examines some common preprocessing directives.

2 Common preprocessing directives

2.1 #include includes header files

#include should be the most common preprocessing directive. Basically, every C file will include several header files through include, or header files are nested to include header files. In the preprocessing stage, the compiler will expand the header files included in the include and write them to the C file. For example, the following C file includes a header file.

//demo.c
#include "demo_type.h"

uint8 demo(uint8 a,uint8 b)
{
    
    
	return a + b;
}
//demo_type.h
typedef unsigned char uint8;

After the preprocessing process, the content of the header file is expanded to the place where the C file includes, and this header file is no longer needed.

//预处理后的demo.c
typedef unsigned char uint8;

uint8 demo(uint8 a,uint8 b)
{
    
    
	return a + b;
}

Thus, this C file can use the types defined in the header file.

Thinking further, the incrementally compiled compiler will recompile the C file that contains the modified header file, so the C file should not contain redundant header files, so as not to increase the compilation time.

For the case of header file nesting, it will be expanded layer by layer.

2.2 #define definition macro

2.2.1 Object-like macros

A macro can be defined through #define. During the preprocessing stage, if a macro is encountered in the code, it will be replaced with the content corresponding to the macro. First look at the code that does not use macro definitions, such as the following code:

//circle.c
float cal_area(float radius)
{
    
    
	return 3.14 * radius * radius;
}

The function takes the radius as input and returns the area of ​​the circle. Among them, pi is used, and the value 3.14 is directly written into the code. Such numbers are called "magic numbers". The correct way is to define it as a macro, and then use this macro in the function.

//circle.c
#define PI 3.14

float cal_area(float radius)
{
    
    
	return PI * radius * radius;
}

This has two advantages. First, when other people read the code, it is difficult to understand the meaning of the number, but the macro definition can literally know the meaning, which can increase the readability of the code. Secondly, if the same value is used in multiple places in the code and needs to be modified (for example, change 3.14 to 3.1415926), you can directly modify the value behind the macro definition.

2.2.2 Function-like macros

Defining a function-like macro also uses #define to define a macro that looks similar to a function. When used, it is like calling a function. For example, the following code:

#include <stdio.h>
#define MAX(a, b)   (((a) < (b)) ? (b) : (a))

int main()
{
    
    
    int a = 1;
    int b = 2;
    int c = MAX(a, b);
    printf("c = %d \r\n", c);
}

MAX(a, b) is used to judge the two parameters a and b passed in, and return the larger value. The fragment of the i file after the code is preprocessed is as follows:

int main()
{
    
    
    int a = 1;
    int b = 2;
    int c = (((a) < (b)) ? (b) : (a));
    printf("c = %d \r\n", c);
}

Here the function-like macro is expanded directly.

From work experience, when the implementation requirements are relatively simple (such as the size above), you can use function-like macros, which can reduce the use of system resources; when you need to implement more complex algorithms, you should still use functions or inline functions. This is more conducive to program debugging.

In addition, there may be some unconsidered problems when using function-like macros. For example, the following code refers to CPrimerPlus.

#include <stdio.h>
#define SQUARE(X) X*X

int main()
{
    
    
    int x = 5;
    printf("x = %d \r\n", x);
    printf("SQUARE(x) = %d \r\n", SQUARE(x));
    printf("SQUARE(x+2) = %d \r\n", SQUARE(x+2));
    printf("100/SQUARE(x) = %d \r\n", 100/SQUARE(x));
    printf("SQUARE(++x) = %d \r\n", SQUARE(++x));
}

The printed result is:
insert image description here
the calculation result of the first SQUARE(x) is correct, but the last three are not as expected. This is because the preprocessor directly replaces characters. The original expression and the preprocessed expression are shown in the following table, which can be easily understood.

macro After preprocessing Calculation results
SQUARE(x+2) x+2*x+2 5+2*5+2 = 17
100/SQUARE(x) 100/x*x 100/5*5 = 100
SQUARE(++x) ++x*++x 7*7 = 47

The third operation above is first to do ++x twice, add x to 7, and then perform multiplication.

Solving the first two problems in the table is simple, just add complete parentheses to the macro, for example as follows:

#include <stdio.h>
#define SQUARE(X) ((X)*(X))

int main()
{
    
    
    int x = 5;
    printf("x = %d \r\n", x);
    printf("SQUARE(x) = %d \r\n", SQUARE(x));
    printf("SQUARE(x+2) = %d \r\n", SQUARE(x+2));
    printf("100/SQUARE(x) = %d \r\n", 100/SQUARE(x));
    printf("SQUARE(++x) = %d \r\n", SQUARE(++x));
}

The printed result is:
insert image description here
But the third self-added problem still cannot be solved, and this method is not recommended in the book.

2.3 Conditional compilation

Conditional compilation is also a commonly used preprocessing instruction. During preprocessing, certain conditions can be used to determine which code blocks to keep. For example, a variable needs to be defined in the code, but it is defined as a global variable during simulation, and it is defined as a local variable during release.

#include <stdio.h>

#ifdef Simulation
int a = 5;
#endif

int main()
{
    
    
#ifndef Simulation
    int a = 10;
#endif
    printf("a = %d \r\n", a);
}

The above code means that when the Simulation macro is defined, the variable a is defined as a global variable and assigned a value of 5; if the Simulation macro is not defined, a is defined as a local variable and assigned a value of 10.

The advantage of this is that the same version of the code is compatible with the two definition methods, and can be switched by defining a macro. Conditional compilation also has many flexible uses.

3 Summary

This article summarizes some preprocessing directives commonly used in the work, as well as examples of use.

>>Back to personal blog catalog

Guess you like

Origin blog.csdn.net/u013288925/article/details/131563383