<C language> Preprocessing and macros

1. Predefined symbols

__FILE__    //进行编译的源文件
__LINE__    //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

These predefined symbols are all built-in in C language.

for example:

#include <stdio.h>

int main() {
    
    
    printf("%s\n", __FILE__);//如:1.c
    printf("%d\n", __LINE__);// 5
    printf("%s\n", __DATE__);// Jul 30 2023
    printf("%s\n", __TIME__);// 10:13:20  记录的时间是编译的时间
    printf("%d\n", __STDC__);   //1   也可能是未定义  不遵循ANSI C
    return 0;
}

2 #define

2.1 #define definition identifier

#defineDefine an identifier of the form:

#define 标识符 值

Among them, 标识符is the name you want to define, and can be a value, a string or an expression.

example:

#include <stdio.h>
#define MAX 100
#define STR "Hello Wrold"
#define do_forever for (;;)
int main() {
    
    
    printf("%d\n", MAX);//100
    printf(STR);        //Hello World
    do_forever;       //死循环

    return 0;
}

#defineJust do simple text replacement, no type checking and no error checking.

It is recommended #definenot to add a semicolon after

#include <stdio.h>
#define MAX 1000;
int main() {
    
    
    int max = 0;
    if (3 > 5) {
    
    
        //max = MAX;   //报错  因为MAX ==1000; 出现了两个分号
        max = MAX//正确
    } else {
    
    
        max = 0;
    }

    return 0;
}

2.2 #define definition macro

The #define mechanism includes a provision that allows parameters to be substituted into the text, and this implementation is often called a macro or a definition macro.

Here is how the macro is declared:

#define name( parament-list ) stuff

The parament-list is a comma-separated list of symbols that may appear in stuff.

example:

#include <stdio.h>

//函数解决
int Max_hanshu(int x, int y) {
    
    
    return x > y ? x : y;
}
//宏解决
#define MAX(x, y) (x > y ? x : y)
int main() {
    
    
    int a = 10;
    int b = 20;
    int max = Max_hanshu(a, b);
    int m = MAX(a, b);
    printf("%d\n", max);  //20
    printf("%d\n", m);    //20
    return 0;
}

Notice:

The opening parenthesis of the parameter list must be immediately adjacent to name.
If any whitespace exists between the two, the argument list is interpreted as part of the stuff.

For example:

#define SQUARE( x ) x * x

This macro receives a parameter x . If after the above declaration, you put

SQUARE( 5 );

Placed in a program, the preprocessor will replace the above expression with the following expression:

5 * 5

WARNING:
There is a problem with this macro:
observe the following code snippet:

int a = 5;
printf("%d\n" ,SQUARE( a + 1) );

At first glance, you might think that this code will print the value 36. In fact, it will print 11.

Why?

When replacing text, the parameter x is replaced with a + 1, so this statement actually becomes:

printf ("%d\n",a + 1 * a + 1 );

This makes it clear that the expressions produced by the substitution are not evaluated in the expected order.

Adding two parentheses to the macro definition solves this problem easily:

#define SQUARE(x) (x) * (x)

After preprocessing in this way, the expected effect is produced:

printf ("%d\n",(a + 1) * (a + 1) );

Here is another macro definition:

#define DOUBLE(x) (x) + (x)

We used parentheses in the definition to avoid the previous problem, but this macro may have new errors.

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

What value will this print?

warning:

It looks like it prints 100, but in fact it prints 55.
We find that after the replacement:

printf ("%d\n",10 * (5) + (5));

The multiplication operation precedes the addition defined by the macro, so 55 appears.

The solution to this problem is to add a pair of parentheses around the macro definition expression.

#define DOUBLE( x)   ( ( x ) + ( x ) )
#include <stdio.h>

#define SQUARE(X) X *X
#define SQUARE1(X) (X) * (X)
#define DOUBLE(X) (X) + (X)
#define DOUBLE1(X) ((X) + (X))
int main() {
    
    
    printf("%d\n", SQUARE(5));     // 25
    printf("%d\n", SQUARE(5 + 1)); // 5+1*5+1 == 11
    printf("%d\n", SQUARE1(5 + 1));// 36

    printf("%d\n", DOUBLE(6));      // 12
    printf("%d\n", DOUBLE(6 + 1));  // 14
    printf("%d\n", 10 * DOUBLE(6)); // 66  10*(6)+(6) ==66
    printf("%d\n", 10 * DOUBLE1(6));//120
    return 0;
}

Summarize:

All macro definitions used to evaluate numeric expressions should be parenthesized in this way to avoid unforeseen interactions between operators in arguments or adjacent operators when using the macro.

2.3 Replacement rules for #define

There are several steps involved when expanding #defines to define symbols and macros in a program.

  • When calling a macro, the arguments are first checked to see if they contain any symbols defined by #define. If yes, they are replaced first.
  • The replacement text is then inserted into the program in place of the original text. For macros, parameter names are replaced by their values.
  • Finally, the resulting file is scanned again to see if it contains any symbols defined by #define. If yes, repeat the above processing.

Notice:

  • Other #define-defined symbols can appear in macro parameters and #define definitions. But with macros, recursion cannot occur.
  • When the preprocessor searches for symbols defined by #define, the contents of string constants are not searched.

Example 1 - Legal macro definition:

#define PI 3.14159
#define CIRCLE_AREA(radius) (PI * (radius) * (radius))

double area = CIRCLE_AREA(2.5); // 宏 CIRCLE_AREA 使用了已定义的宏 PI

Example 2 - Illegal macro definition (recursive):

// 这是一个非法的宏定义,宏 AREA 使用了它自身
#define AREA(x) (x > 0 ? x * x : AREA(x))

int result = AREA(5); // 这将导致宏展开的无限循环,造成编译错误

2.4 # and ##

#operator can convert macro arguments to string constants. It allows you to convert arguments to string literals in macro definitions .

Example:

#define STRINGIFY(x) #x

int main() {
    
    
    int num = 42;
    const char* str = STRINGIFY(num);
    // 在宏展开时,num 被转换为字符串 "42"
    printf("num as a string: %s\n", str); // Output: "num as a string: 42"
    return 0;
}

##operator is used to paste two tokens together in a macro definition. It allows you to combine multiple identifiers into a new identifier .

Example:

#define CONCAT(x, y) x ## y

int main() {
    
    
    int num1 = 10;
    int num2 = 20;
    int result = CONCAT(num, 1) + CONCAT(num, 2);
    // 在宏展开时,CONCAT(num, 1) 变成 num1,CONCAT(num, 2) 变成 num2
    // 所以,result 的值就是 num1 + num2,即 10 + 20
    printf("result: %d\n", result); // Output: "result: 30"
    return 0;
}

2.5 Macro arguments with side effects

When a macro parameter appears more than once in the definition of the macro, if the parameter has side effects, then you may be dangerous when using this macro, resulting in unpredictable consequences. Side effects are permanent effects that occur when an expression is evaluated.

Code with side effects :

int main() {
    
    
    int a = 1;
    int b = a + 1;// b=2,a=1
    a = 1;
    b = ++a;// b=2,a=2  带有副作用的代码,a的值发生了改变
    int ch = getchar();//读一个字符,缓冲区少一个字符

    return 0;
}
x+1;//不带副作用
x++;//带有副作用

The MAX macro can demonstrate problems caused by parameters with side effects.

#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);//输出的结果是什么?

Here we need to know what the result of the preprocessor is:

z = ( (x++) > (y++) ? (x++) : (y++));

So the output is:

x=6 y=10 z=9

2.6 Comparison of macros and functions

Macros are usually used to perform simple operations. For example, find the larger of two numbers.

So why not use a function for this task?

There are two reasons:

1. The code used to call functions and return from functions may take more time than it takes to actually perform this small computational work.
So macros are better than functions in terms of program size and speed.

2. More importantly, the parameters of the function must be declared as specific types. So functions can only be used on expressions of the appropriate type. On the contrary, how can this macro be applicable to types such as integers, long integers, and floating-point types that can be used for > to compare. Macros are type independent.

Example:

#include <stdio.h>

int Max(int x, int y) {
    
    
    return x > y ? x : y;
}

#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main() {
    
    
    int a = 10;
    int b = 20;
    //函数的方式
    int m1 = Max(a, b);
    printf("%d\n", m1);

    //宏的方式
    int m2 = MAX(a, b);
    printf("%d\n", m2);
    return 0;
}

Disadvantages of macros : Of course, compared with functions, macros also have disadvantages:

  1. Every time a macro is used, a copy of the code defined by the macro is inserted into the program. Unless the macro is relatively short, it can increase the length of the program considerably.

  2. Macros cannot be debugged.

  3. Macros are not rigorous enough because they are type-independent.

  4. Macros may introduce operator precedence issues, making programs prone to errors.

Macros can sometimes do things that functions cannot. For example: macro parameters can have types , but functions cannot.

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
int main(){
    
    
    int *p = malloc(10, sizeof(int));
    MALLOC(10, int);  //类型作为参数
    return 0;
} 

A comparison of macros and functions

Attributes #definemacros function
code length Macro code is inserted into the program each time it is used. Except for very small macros, the length of the program can grow substantially The function code appears in only one place; every time the function is used, the same code in that place is called
execution speed faster There is additional overhead for function calls and returns, so it is relatively slow
operator precedence The evaluation of macro parameters is in the context of all surrounding expressions. Unless parentheses are added, the precedence of adjacent operators may produce unpredictable results, so it is recommended to use more parentheses when writing macros. Function parameters are evaluated only once when the function is called, and its resulting value is passed to the function. The result of evaluating an expression is more predictable.
Parameters with side effects Parameters may be substituted in multiple places within the macro body, so parameter evaluation with side effects may produce unpredictable results. Function parameters are only evaluated once when they are passed, and the result is easier to control.
Parameter Type The parameter of macro has nothing to do with the type, as long as the operation on the parameter is legal, it can be used for any parameter type. The parameters of a function are related to the type. If the types of the parameters are different, different functions are required, even if they perform the same task.
debugging Macros are inconvenient to debug Functions can be debugged statement by statement
recursion Macros cannot be recursive functions can be recursive

2.7 Naming Conventions

In general, the usage syntax of function macros is very similar. So language itself cannot help us distinguish between the two. Then one of our usual habits is:

capitalize macro names

Do not use all uppercase function names

3.#undef

This command is used to remove a macro definition.

#include <stdio.h>

#define M 100
int main() {
    
    
    printf("%d\n");
#undef M
    printf("%d\n", M);//报错,M的宏定义已经被移除
    return 0;
}

4. Command line definition

C language is a general-purpose programming language that allows developers to interact with computers by writing command-line programs. Command-line programs are programs that run in a command-line interface (also known as a terminal or command prompt). Users can invoke these programs by entering commands and parameters, and obtain results from the program's output.

In C language, the command line parameters are passed to the program through the parameters of the main function. The main function is the entry point of the C program, it has two parameters: argcand argv.

  1. argc: Indicates the number of command line arguments, including the program itself. It is a variable of integer type.
  2. argv: is a pointer to an array of character pointers, used to store strings of command-line arguments. Each string represents a command-line argument. Among them, argv[0] stores the name of the program (the name of the executable file), argv[1] stores the first command line parameter, and so on.
int main(int argc, char *argv[]) {
    
    
    // Your code here
    return 0;
}

Example explanation:

Assuming we have a program called "my_program", the compiled executable file "my_program.exe" (on Windows), and then we run the program in the command line, the input is as follows:

my_program hello world

In this example, the value of argc will be 4, since there are four arguments: the program name "my_program", "hello", "world", and an implicit null pointer indicating the end of the string.

The argv array will contain the following:

argv[0] -> "my_program"
argv[1] -> "hello"
argv[2] -> "world"
argv[3] -> NULL
#include <stdio.h>

int main(int argc, char *argv[]) {
    
    
    for (int i = 0; i < argc; i++) {
    
    
        printf("Argument %d: %s\n", i, argv[i]);
    }
    return 0;
}

Output result:

Argument 0: my_program
Argument 1: hello
Argument 2: world

5. Conditional compilation

Conditional compilation is a preprocessing directive that allows code fragments to be selectively included or excluded based on different conditions during the compilation phase. Conditional compilation can be used to control the behavior of a program based on different compilation conditions, such as using different code on different platforms or enabling/disabling specific features.

Conditional compilation is implemented using preprocessing directives #ifdef, #ifndef, #else, #endif, #if, , #elifand #defineso on. These directives all start with a pound sign (#) and are processed by the preprocessor before compilation.

The following are the basic instructions for conditional compilation in C language:

1. #ifdefand #ifndef:

#ifdef 宏名
    // 如果宏已定义,则编译这里的代码
#else
    // 如果宏未定义,则编译这里的代码
#endif

#ifdefIt is used to check whether a macro has been defined, if it is defined, then compile the code #ifdefbetween #endifand , otherwise skip this part of the code.

#ifndefThen instead #ifdefof , it checks whether a macro is undefined, and if so, compiles the code between #ifndefand .#endif

2.#else

#ifdef 宏名
    // 如果宏已定义,则编译这里的代码
#else
    // 如果宏未定义,则编译这里的代码
#endif

#elseUsed to compile the code between and when the #ifdefor conditions are not met .#ifndef#else#endif

3. #if, #elifand #endif:

#if 表达式
    // 如果表达式为真,则编译这里的代码
#elif 其他表达式
    // 如果其他表达式为真,则编译这里的代码
#else
    // 如果前面的条件都不满足,则编译这里的代码
#endif

#ifAllows you to decide whether to compile the following code based on the result of an expression. #elifIt is used to check other conditions when the previous conditions are not met. #elseIt is used to deal with the situation that none of the previous conditions are satisfied.

nested conditional compilation

#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif

6. The file contains

6.1 How header files are included

1. The local file contains " "

Search strategy: First search in the directory where the source file is located. If the header file is not found, the compiler will search for the header file in the standard location just like searching for the library function header file.

If not found, a compilation error will be prompted.

#include"add.h"
int main(){
    
    
    printf("hehe\n");
    return 0;
}

2. The library file contains < >

To find the header file, go directly to the standard path to find it. If it cannot find it, it will prompt a compilation error.

In this way, it can be said that library files can also be included in the form of " "?
The answer is yes, you can.

But the efficiency of searching in this way is lower. Of course, it is not easy to distinguish whether it is a library file or a local file.

6.2 Nested file includes

insert image description here

comm.h and comm.c are common modules.
test1.h and test1.c use common modules.
test2.h and test2.c use common modules.
test.h and test.c use the test1 module and test2 module.
In this way, two copies of comm.h will appear in the final program. This creates duplication of file content.

how to solve this problem?
Answer: conditional compilation.

At the beginning of each header file write:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H__

or:

#pragma once

Guess you like

Origin blog.csdn.net/ikun66666/article/details/132005546