[C/C++] Let you know more about preprocessing!

This blog is a derivative of the previous blog [C/C++] program environment, exploring the execution process of the program. Some knowledge here needs to be used in this blog. If you don’t know about it, you can check it here.

1. Predefined symbols

There are some predefined symbols that can be used directly in the preprocessing stage, as follows:

__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
//当写代码时想要获得文件位置、行号、日期和时间可以使用以上命令

Example 1:

int main()
{
    
    
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);

	return 0;
}

insert image description here
We can clearly see the role of these predefined symbols.

Example 2:

int main()
{
    
    
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __STDC__);

	return 0;
}
//报出如下错误
error C2065: “__STDC__”: 未声明的标识符
  • I am using VS2019, as can be seen from the error report, the compiler does not follow ANSIC

2.#define

The role of #define is in the following two points

  1. #define define identifier
  2. #define Defining macros

2.1#define definition identifier

Syntax: #define name stuff

Example:

#define MAX 1000

#define reg register //为 register这个关键字,创建一个简短的名字

#define do_forever for(;;) //用更形象的符号来替换一种实现//替换后为死循环

#define CASE break;case //在写case语句的时候自动把 break写上。

// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )

Ask:

When defining an identifier, should you add " ; " after it?
You can use an example to answer this question:

#define MAX 100;
#define MAX 100
//建议不要加符号

//在以下场景下会报错
if(condition)
	max = MAX;
	//添加分号,替换后为:max = MAX;;表示有两条语句
	//在if下如果有两条或两条以上语句需要使用用大括号,否则报错
else
	max = 0;
  • If you want to add" ; "you need to pay attention to this problem.

2.2#define definition macro

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

Here's how to declare a macro:

#define name(parament-list) stuff
  • parament-list is a comma-separated list of symbols that may appear in stuff.

Example:

#define Max(a,b) ((a)>(b))?(a):(b)

int main()
{
    
    
	int a = 10;
	int b = 20;
	int c = Max(a, b);
    //替换后为:int c = ((a)>(b))?(a):(b);
	printf("%d\n", c);

	return 0;
}

insert image description here
Note:
The left parenthesis of the parameter list must be adjacent to name.
If any whitespace exists between the two, the argument list is freed as part of stuff.
Such as:

#define AQUARE(x) x*x

2.3 #define replacement rules

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

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

Notice:

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

printf("hello M\n")
//在上诉字符串中有M,但不识别。

2.4 '#' and '##'

How to pass parameters into a string

Let's take a look at the following code first:

int main()
{
    
    
	char* p = "hello ""world\n";
	printf("hello"" world\n");
	printf("%s", p);

	return 0;
}

insert image description here

  • When we separate this string and use two semicolons to include it, the desired result can still appear.
  • We can see through this code that the string is automatically connected.

# role

#define PRINT(value,format) printf("the "#value" is "format"\n", value)

int main()
{
    
    
	int a = 10;
	PRINT(a, "%d");

	int b = 20;
	PRINT(b, "%d");

	float c = 3.2f;
	PRINT(c, "%f");

	return 0;
}

insert image description here

  • Here we can see that using "#" turns a macro parameter into a corresponding string.

## role

## The symbols at both ends can be combined into one symbol.
It allows macro definitions to create identifiers from separated text fragments.

#define CAT(A,B) A##B

int main()
{
    
    
	int AAABBB = 100;
	printf("%d\n", CAT(AAA, BBB));

	return 0;
}

insert image description here

  • Such concatenation must result in a valid identifier, otherwise the result is invalid.

2.5 Macro parameters with side effects

//方法1
int a = 1;
int b = a + 1;

//方法2
int a = 1;
int b = ++a;
  • Method 2 is code with side effects. While assigning a value to b, the value of a is changed
  • With side effects means that when a certain block is completed, other areas are also affected

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

#define MAX(a,b) ((a)>(b)?(a):(b))

int main()
{
    
    
	int x = 4;
	int y = 5;
	int z = MAX(x++, y++);
	printf("z=%d x=%d y=%d\n", z, x, y);

	return 0;
}

insert image description here

  • This code has side effects. When the result is output, the values ​​​​of x and y are changed at the same time.

2.6 Comparison of macros and functions

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

#define MAX(a,b) ((a)>(b)?(a):(b))

Why not use a function for this task?

Advantages of macros

  1. The code to call and return from a function 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.

For simple calculations, function calls need to be stacked and destroyed, and there are very few actual running operations.
The operation of the macro is to operate directly without other operations, which is more convenient than the function.
insert image description here

  1. 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, this macro is suitable for types that can be used for comparison such as integers, long integers, and floating-point types.
    Macros are type independent.

Disadvantages of macros:

  1. Each time a macro is invoked, 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.

    There is only one copy of the function, and it is used where each call is made.

  2. Macro cannot be debugged
  3. Macros are not rigorous enough because they are type-independent.
  4. Macros can introduce operator precedence issues, making programs prone to bugs.

Macros can do things that functions can't

Macro parameters can have types, but functions cannot.

#define CALLOC(num,type) (type*)calloc(num,sizeof(type))

int main()
{
    
    
	int* a = CALLOC(10, int);

	return 0;
}

insert image description here

Comparing Macros and Functions

  1. code length

    Macros defined by #define: Macro code is inserted into the program each time it is used. Except for very small macros, the length of the program can grow considerably.
    Functions: Function code appears in only one place; every time the function is used, the same code in that place is called.

  2. execution speed

    Macros defined by #define: Faster
    Function: There is additional overhead for function calls and returns, so it will be relatively slower.
    But for larger programs, this time does not affect.

  3. Operator symbol precedence

    Macros defined by #define: macro parameters are evaluated in the context of all surrounding expressions. Unless parentheses are added, the priority of adjacent operators may have unpredictable consequences, so it is recommended to write more macros brackets.
    Function: A function parameter is evaluated only once when the function is called, and its resulting value is passed to the function.

  4. Parameters with side effects

    Macros defined by #define: parameters may be substituted into multiple positions in the macro body, so parameter evaluation with side effects may produce unpredictable results.
    Function: Function parameters are only evaluated once when passing parameters, and the result is easier to control.

  5. Parameter Type

    Macro defined by #define: The parameter of the 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.
    Function: 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.

	int x = 10;
	int y = 20;
	ADD(x + y);//宏——将括号内的三个元素作为参数传递
	add(x + y);//函数——将x和y相加后作为一个参数传递
  1. debugging

    Macros defined by #define: Macros are inconvenient to debug
    Functions: The parameters of functions are related to types. If the types of parameters are different, different functions are required, even if they perform the same tasks.

  2. recursion

    Macros defined by #define: Macros cannot be recursive
    Functions: Functions can be recursive

2.7 Command Conventions

Generally speaking, the usage syntax of functions and macros is very similar, so the language itself cannot help us distinguish between the two.
Our usual habits are:

Capitalize all macro names
and not all capitalize function names

But there is an exception:

offset  —— 宏
getchar —— 有些编译器上,也是宏

3.#undef

This command is used to remove a macro definition.

#undef NAME
//如果现存的一个名字需要被重定义,那么它的旧名字首先要被移除。

insert image description here

  • As shown in the figure, the first M can be used normally, but the second M cannot be used after using #undef.

4. Command line definition

  • It cannot be demonstrated in the VS environment, and needs to be demonstrated in the Linux environment.
#include <stdio.h>
int main()
{
    
    
	int array[ARRAY_SIZE];
	int i = 0;
	for (i = 0; i < ARRAY_SIZE; i++)
	{
    
    
		array[i] = i;
	}
	for (i = 0; i < ARRAY_SIZE; i++)
	{
    
    
		printf("%d ", array[i]);
	}
	printf("\n");
	return 0;
}

Many compilers provide an ability to allow symbols to be defined on the command line. Used to start the compilation process.

For example: this feature is useful when we want to compile different versions of a program based on the same source file.

Looking at the above code, suppose a certain program declares a very long array. If the memory of the machine is limited, we need a small array, but if the memory of another machine is larger, we need an array that can be larger.

  • We enter commands on the command line to change the size of the array array.
//linux环境演示:
gcc test.c -D ARRAY_SIZE=10
//test.c为代码所在源文件

There is a test.c before this directory, so name the file as test1.c
insert image description here

5. Conditional compilation

When compiling a program, it is very convenient if we want to compile or discard a statement (a group of statements). Because we have conditional compilation directives.

Debugging code (code written for the convenience of debugging), it is a pity to delete it (in case you encounter the same problem next time), and keep it in the way, so we can compile it selectively.

Example:

#include <stdio.h>
#define __DEBUG__

int main()
{
    
    
	int i = 0;
	int arr[10] = {
    
     0 };
	for (i = 0; i < 10; i++)
	{
    
    
		arr[i] = i;
//如果标识符被定义,则执行条件语句。
#ifdef __DEBUG__
			printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

insert image description here
Example 2:

#include <stdio.h>
//#define __DEBUG__

int main()
{
    
    
	int i = 0;
	int arr[10] = {
    
     0 };
	for (i = 0; i < 10; i++)
	{
    
    
		arr[i] = i;
#ifdef __DEBUG__
			printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

insert image description here

  • As shown above, when _DEBUG_ (identifier, the name is random) is defined, the conditional compilation instruction #ifdef _DEBUG_ can be used to start, and #endif can be used to end.

Common conditional compilation directives:

1.
#if 常量表达式
	//...
#endif
//常量表达式由预处理器求值。
    
如:
#define __DEBUG__ 1
#if __DEBUG__
	//..
#endif

2.多个分支的条件编译
#if 常量表达式
	//...
#elif 常量表达式
	//...
#else
	//...
#endif

3.判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol

4.嵌套指令
#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
  • These conditional compilation instructions are not difficult to understand, just substitute them into the application.

6. The file contains

We already know that the #define directive can cause another file to be compiled. Just like where it actually appears in the #define directive.
This replacement is simple:

The preprocessor first removes this directive and replaces it with the contents of the include file.
Such a source file is included 10 times, and it is actually compiled 10 times.

6.1 How header files are included

(1) The local file contains

#define "filename"

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.
Report an error if you can't find it.

(2) The library file contains

#include<filename.h>

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.

Can it be said that the form of "" can also be used for library files?
Answer: Yes
, but the efficiency of searching in this way is lower. Of course, it is not easy to distinguish whether it is a library function or a local file.

6.2 Nested file contains

insert image description here

comm.h and comm.c are public modules
test1.h and test1.c use the public module
test2.h and test2.c use the public module
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.

Method 1:
Use conditional compilation to determine whether the header file is included multiple times.

Method 2:

#pragma once
  • Both methods prevent header files from being included repeatedly.

Guess you like

Origin blog.csdn.net/m0_52094687/article/details/127515328