"C and Pointers" Reading Notes (Chapter 14 Preprocessor)

0 Introduction

Compiling a C program involves many steps, the first of which is the preprocessing stage ( preprocessing) stage. The C preprocessor performs some textual operations on source code before it is compiled. Its main tasks include removing comments, inserting #includingthe contents of files included by directives, defining and replacing symbols defined by #define directives, and determining whether parts of the code should be compiled according to some conditional compilation directives.

Content framework of this issue:
Insert image description here

1 predefined symbols

The preprocessor defines some symbols, which provides great convenience for our program debugging and version generation. for example:

#include <stdio.h>

int main()
{
    
    

	printf("This obj file name is:%s\n",__FILE__);
	printf("Current line is:%d\n", __LINE__);
	printf("-------------------------\n");
	printf("Today is:%s\n", __DATE__);
	printf("The time now is:%s\n", __TIME__);


	system("pause");
	return 0;
}

Print output:
Insert image description here
You can see that the name of the file we are currently running, the line number of the print statement, and the date and time of compilation are all printed.

Note: The date and time here are the date and time the file was compiled, not the date and time when it was run.

2 #define

Chapter 2 is our top priority, which contains the method, meaning and things to pay attention to when defining macros.

2.1 Macros

The #define mechanism includes a provision that allows parameters to be substituted into text. This implementation is often called a macro ( macro) or a define macro ( defined macro).
For example, we use a macro to define a square operation:

#include <stdio.h>
#define SQUARE(x) x*x
int main()
{
    
    
	int a = 5;
	printf("%d\n",SQUARE(a));

	system("pause");
	return 0;
}

Print output:
Insert image description here
But sometimes there will be problems with this method, for example:

#include <stdio.h>
#define SQUARE(x) x*x
int main()
{
    
    
	int a = 5;
	printf("%d\n",SQUARE(a + 1));

	system("pause");
	return 0;
}

Print output:
Insert image description here
According to our expectation, the square of 6, which is 36, should be output. Why is this?

Different from the implementation of functions, macros only perform simple replacements, but do not do any work such as parameter passing.
You can make a comparison:

#include <stdio.h>
#define SQUARE(x) x*x
int square(const int x)
{
    
    
	return x * x;
}
int main()
{
    
    
	int a = 5;
	printf("%d\n", SQUARE(a + 1));
	printf("%d\n", square(a + 1));

	system("pause");
	return 0;
}

Print output:
Insert image description here
It can be found that if it is in the form of a function, the execution result is consistent with our idea. This is because the macro only does a simple replacement work, that is to say, after execution, the natural running result 5+1*5+1is 11.

But we can also use macro definitions to achieve the same results as expected:

#include <stdio.h>
#define SQUARE(x) (x)*(x)
int square(const int x)
{
    
    
	return x * x;
}
int main()
{
    
    
	int a = 5;
	printf("%d\n", SQUARE(a + 1));
	printf("%d\n", square(a + 1));

	system("pause");
	return 0;
}

Printout:
Insert image description here
Problem solved!

2.2 #define replacement

This part mainly talks about how to insert macro parameters into string constants. For example, we need to use macros to redefine a printing function:

#include <stdio.h>
#define PRINT(FORMAT, VALUE)  \
		printf("The value of "VALUE" is "FORMAT"\n",VALUE)
int main()
{
    
    
	int a = 5;
	PRINT("%d", a);
	PRINT("%d", a + 3);

	system("pause");
	return 0;
}

At this time, the program will report an error because the value cannot be passed accurately. At this time, we need to perform a simple conversion to convert the incoming expression into a string.

#include <stdio.h>
#define PRINT(FORMAT, VALUE)  \
		printf("The value of "#VALUE" is "FORMAT"\n",VALUE)
int main()
{
    
    
	int a = 5;
	PRINT("%d", a);
	PRINT("%d", a + 3);

	system("pause");
	return 0;
}

Print output
Insert image description here
At this time, the advantages of macro definition are revealed. It is found that to realize such a function, the function encapsulation is a little weak.

2.3 Macros and functions

From the above examples, we can find that in many cases, macros and functions have similar functions and can be replaced with each other. But there are differences between the two. There is a table in the book that looks very clear:

Attributes #define macro function
code length It will be inserted into the program every time it is used, so the content of the macro should not be too much Each function call uses the same code, which is relatively more memory-friendly.
Execution speed faster There is additional overhead of function calls/returns
operator precedence Macro parameters are evaluated in the context of all surrounding expressions, so parentheses must be included. The result of an expression is more predictable
Parameter evaluation Parameters are re-evaluated each time they are used in a macro definition. Parameters with side effects may produce unpredictable results due to multiple evaluations Parameter side effects do not cause any special problems
Parameter Type Macros are type-independent and can use any parameter type as long as the operation is legal. The parameters of a function are strongly related to their types. If the types of parameters are different, different functions need to be defined, or the actual parameters need to be cast.

It can be seen that macros and functions each have their own advantages and disadvantages, so choosing the appropriate implementation method in the right occasion and tailoring it is the best choice.

2.4 Macro parameters with side effects

When parameters have side effects, our macros can often produce unexpected results, which requires special attention in our actual development. Take the following example:

#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{
    
    
	int x = 2, y = 5, z = 0;
	z = MAX(x++, y++);
	printf("x = %d, y = %d, z = %d",x, y, z);
	system("pause");
	return 0;
}

What should the value of z be? If this maxis implemented using a function, the answer can definitely be easily obtained. However, it seems not so simple in the macro definition, because the macro definition requires replacing the sum with two expressions many atimes b.

But after thinking more about it, I found that it is not difficult. x++It was executed twice and y++executed three times, so the answer is clear at a glance. Printout:
Insert image description here
So, what if it is ++x and ++y? We can explore it ourselves. . .

2.5 Naming Convention

Many times, for better development, it is necessary to formally distinguish macros and functions. A common way is to make the macro name in all capital letters. I believe many people have experienced this during development.

2.6 #undef

This preprocessor directive removes a macro definition.
Declaration method:

#undef name

If an existing name needs to be redefined, its old definition must first be #undefremoved.

2.7 Command line definition

Many C compilers provide the ability to define symbols on the command line that can be used to start the compilation process.

In common compilers for Windows, the relevant content is not used, so it is omitted.

3 Conditional compilation

3.1 Whether it is defined

3.2 Nested instructions

Even in large-scale project development, nested instructions are rarely used. And it’s not difficult to understand even if you encounter it. The basic form is given in the book, so I won’t go into details in this article.

#ifdef OS_UNIX
	#ifdef OPTION1
		unix_version_of_option1();
	#endif
	#ifdef OPTION2
		unix_version_of_option2();
	#endif
#elif defined OS_MSDOS
	#ifdef OPTION2
		msdos_version_of_option2();
	#endif	
#endif

It is similar to the nesting of conditional statements, so it is relatively easy to read.

4 files contain

In actual development, larger projects will adopt modular development, which can avoid a lot of trouble. It makes code development, iteration, and reading much more convenient.

andFile containsThis idea is well explained.

4.1 Function library files include

The so-called function library files are some programs (usually functions) that have been written for us before we program. We sometimes call it an interface. We generally use angle brackets for inclusion. As in our previous procedure:

#include <stdio.h>

In this way, the computer can recognize printfsuch statements when the program is compiled and run.

4.2 Local file inclusion

The so-called local file inclusion generally refers to local files written by ourselves. For example:
we create a print.cfile and write the following function:

#include <stdio.h>
#include "print.h"
void fun_print(char *x)
{
    
    
	if (x != NULL)
	{
    
    
		printf("%s",x);
	}
}

Then create print.hthe file and declare it:

#pragma once
void fun_print(char *x);

Finally, main.cmake the call in the file:

#include <stdio.h>
#include "print.h"
int main()
{
    
    
	fun_print("Hello world!");
	system("pause");
	return 0;
}

This is local file inclusion. In order to distinguish it from system file inclusion, double quotes are generally used .

4.3 Nested file inclusion

In large-scale project development, there are a large number of source files and header files, and obviously there are intricate inclusion relationships. Nested inclusion relationships have become a common phenomenon. This is nothing in itself, but if there are multiple inclusions, phenomenon, the compiler will report an error during compilation and the compilation cannot pass.

In other words, multiple inclusions will directly affect the running efficiency or memory size of the program. For specific content, please check relevant information. So how to avoid this problem?
Generally, in order to prevent multiple inclusions of header files and multiple definitions during compilation, we can adopt the following method:

#ifndef __HEADERNAME__H
#define __HEADERNAME__H 1

or

#pragma once

#pragma once is a preprocessing directive used for header file protection. Its role is to ensure that the same file is not included multiple times to avoid causing redefinition errors during the compilation process. It can be said that its effect is similar to the combination of #ifndef and #define. Compared with the #ifndef method, #pragma once is more concise and efficient, and can protect the entire file.

However, it should be noted that #pragma once is a non-standard method. Although it is widely supported by most compilers, it also has compatibility issues. Some older compilers may not support #pragma once, so you need to consider project compatibility when choosing to use it.

To sum up, the choice of which method to use depends on the specific situation and can be agreed upon according to the team's development specifications. Both approaches are acceptable as long as the disadvantages can be reasonably avoided.

5 other instructions

#errorIt is one of the C language preprocessing instructions. It checks whether there are errors in the source code during the compilation phase and outputs error information during compilation. When the compiler encounters the #error directive, it will stop compilation and display the error message after #error.

#include <stdio.h>
#include "print.h"	
int main()
{
    
    
#ifdef PRINT
	fun_print("Hello world!");
#else
	#error Print is not defined!
#endif
	system("pause");
	return 0;
}

Click Run and find that compilation cannot be performed and the error content is output directly:
Insert image description here
Of course, different IDEs may report errors in different forms.

#progmaDirectives are another mechanism for supporting compiler-specific features. For example, what we mentioned earlier

#pragma once

This prevents header files from being included again.

So-calledSupport for compiler-specific featuresThat is to say, the same #pragmainstruction may produce different effects in different compilers, depending on the specific situation.

6 Summary

The first step in compiling a C program is to preprocess it. The preprocessor supports a total of 5 symbols.

The #define directive can be used to "rewrite" the C language to make it look like another language.

Conditional compilation can execute different code segments under different conditions. This is much more convenient than large-area masking programs and is widely used in the development of large projects.

In actual development, try to avoid multiple inclusions of header files, although sometimes the development environment does not directly report errors or warnings.

Guess you like

Origin blog.csdn.net/weixin_43719763/article/details/132798855