c language preprocessing during the national day without rest, overtaking on the curve is enough


Program translation environment

  • In any implementation of ANSI C, there are two different environments for program translation and execution.

  • The first is the translation environment, in which the source code is converted into executable machine instructions.

  • The second is the execution environment, which is used to actually execute code.


  • Suppose you want to process a simple addition calculation of 2 + 3, your program will be precompiled, compiled, assembled and finally linked to generate an executable program, and the program will run and output result 5

Detailed compilation + link

Translation environment:


A c language source file will be processed by the compiler (precompiled, compiled and assembled) to generate an object file (. obj), and then the generated object file will be finally linked to a piece of generated executable program through the link library. Under vs, its compiler is cl.exe and the linker is link.exe

carding:

  • 1. A source file needs to be linked and compiled to generate an executable program, and finally it can run.
  • 2. The compilation is divided into pre compilation + compilation + assembly to generate the target file. Finally, after the processing of the linker and the link library, the target file is linked together to form an executable program

What is a link library?
Suppose you want to use a library function, which will be included in the library. When you want to use this function, these libraries will be linked to your project

What does program precompiling do?

Preprocessing: it is equivalent to assembling a new C/C + + program according to preprocessing instructions. After preprocessing, an output file without header (all expanded), macro definitions (all replaced), conditional compilation instructions (all shielded), and no special symbols will be generated. The meaning of this file is the same as that of the original file, but the content is different.

Compilation: after a series of lexical analysis, syntax analysis, semantic analysis and optimization of the preprocessed files one by one, the corresponding assembly code files are generated. Compilation is for a single file. It only verifies whether there is a problem with the syntax of this file, and is not responsible for finding entities.

Link: a complete executable program is generated by linking one object file (and perhaps a library file) together through a linker.
The main work of the linker is to connect the relevant object files with each other, that is, to connect the symbols referenced in one file with the definition of the symbols in another file, so that all these object files can be loaded and executed by the operating system. During this process, you will find that the called function is undefined. It should be noted that in the link phase, only the called function / global variable will be linked. If there is a declaration without an entity (function declaration and external declaration of global variable), but it is not called, it can still be compiled and executed normally.

Text operation

  • 1. Include of #include header file
  • 2. #define defines the substitution of symbols
  • 3. Delete comment

What does program compilation do?

Convert c language code into assembly code

  • 1. Syntax analysis
  • 2. Lexical analysis
  • 3. Semantic analysis
  • 4. Symbol summary

What does the assembly process do

  • 1. The assembly code is converted into two-level instructions (machine instructions),
  • 2. Form symbol table

What did the linking process do

  • 1. Consolidated segment table
  • 2. Merging and relocation of symbol tables

Program execution environment

Operating environment

  1. The program must be loaded into memory. In an operating system environment: This is usually done by the operating system.
  2. In an independent environment, the loading of the program must be arranged manually, or it may be completed by putting the executable code into the read-only memory.
  3. The execution of the program begins. The main function is then called.
  4. Start executing program code. At this time, the program will use a run-time stack to store the local variables and return addresses of the function. Programs can also use static memory. Variables stored in static memory keep their values throughout the execution of the program.
  5. Terminate the procedure. Normally terminate the main function; It can also be an accidental termination

Pretreatment details

predefined symbol

  • FILE / / source FILE for compilation

  • LINE / / the current LINE number of the file

  • DATE / / the DATE the file was compiled

  • TIME / / the TIME when the file was compiled

  • STDC / / if the compiler follows ANSI C, its value is 1, otherwise it is undefined

Here are a few predefined symbols to show readers the test results

A test code

void functest()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	for (int i = 0; i < 10; i++) 
	{
		//Output the data to a file in a formatted form
		printf("file:%s line:%d arr[%d] = %d\n",
		 __FILE__, __LINE__,i,arr[i]);
	}
}

Here you can not only see the file under which the program is compiled, but also print the line where printf is now

Use more sets of predefined symbols

The function writes formatted data to a file

void functest()
{
	FILE *pf = fopen("C:\\Users\\26961\\Desktop\\data.txt","w");
	if (pf == NULL)
	{
		perror("fopen: file");
		exit(-1);
	}
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};

	for (int i = 0; i < 10; i++) 
	{
		//Output the data to a file in a formatted form
		fprintf(pf,"file:%s line:%d date: %s time : %s arr[%d] = %d\n", __FILE__, __LINE__, __DATE__, __TIME__,i,arr[i]);
	}
}

Presented effect

#define

#define definition identifier

Syntax:
 #define int INT

#define defines int, which is equivalent to giving int a small name called int, but int and int are the same person. In the preprocessing stage, it is only to replace int with int

A variety of macro definitions you haven't used

#define reg register / / use the keyword register and take the alias reg
#define do_forever for(;) / / replace an implementation with a more vivid symbol
#define CASE break;case / / when writing a case statement, the break is automatically written.
// If the defined stuff is too long, it can be written in several lines. Except for the last line, a backslash (continuation character) is added after each line.
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,       \
                          __DATE__,__TIME__ )

Explain #define CASE break;case

#define CASE break;case
void functest()
{
	int input = 0;
	scanf("%d",&input);
	switch (input) 
	{
		case 1:
			//sentence
		CASE 2 :
			//sentence
			//Break after pretreatment deployment; case 2:
		CASE 3 :
			//sentence
			After preprocessing deployment  break;case 3:
		CASE 4 :
			//sentence
			After preprocessing deployment  break;case 4:
		;  //Semicolon end
	}
}

When using the switch statement, #define CASE break;case is feasible, and the symbol defined by #define will be replaced by break;case, but it is not recommended to write it in this way. It is not readable, but it is really a powerful feature of #define

Explain #define do_forever for(; ; )

#define do_forever for(;;)
int main()
{
	do_forever for(;;)
	printf("hello c");
}

Replace an implementation with a more vivid symbol. The function of the implementation is to print hello c circularly, and the program will not terminate

Look at another one

 #define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,)\
                          __DATE__,__TIME__ )

The backslash in the back indicates a continuation character, which can only appear when writing #define to define the identifier. However, it is worth noting that you should never add spaces and carriage return after the continuation character. If the backslash is combined with carriage return, it will become an escape character, so your program may have problems. Just use it carefully

Error demonstration

#define MAX 1000;
void functest()
{
	int max = 0;
	if (1)
		max = MAX;
		//max = 1000 after Hongzhan;;
	else
		max = 0;
}

The if statement does not contain {}, so it is only valid for one line of statements, but max = 1000 after expansion, In c language, it can be seen that what ends with a semicolon is an empty statement, which will lead to the problem that if and else do not match, unless max = MAX with {}; Enclose

Macro definition with parameters

#The define mechanism includes a provision that allows parameters to be replaced with text. This implementation is often called macro or define macro

A deceptive way of writing

#define SQUARE(x) x * x
void functest()
{
	printf("%d \n", SQUARE(5));
	printf("%d \n", SQUARE(4 + 1));
}

There's really no problem with writing the first, but when writing the second, it will be replaced with
4 + 1 * 4 + 1 = 9, because 4 + 1 will directly replace x in the pretreatment stage, and x is not regarded as a whole
Slightly modified

#define SQUARE(x) (x) * (x)
void functest()
{
	printf("%d \n", SQUARE(5));
	printf("%d \n", SQUARE(4 + 1));
}

(x) * (x) is (4 + 1) * (4 + 1) even if it is replaced, which will not affect the final result, because X has been regarded as a whole

#define replacement rule

  • Extending #define to define symbols and macros in a program involves several steps.

  • 1. When calling a macro, first check the parameters to see if they contain any symbols defined by #define. If so, they are replaced first.

  • 2. The replacement text is then inserted into the position of the original text in the program. For macros, parameter names are replaced by their values.

  • 3. Finally, scan the result file again to see if it contains any symbols defined by #define. If so, repeat the above process.

#And##

Use #, to change a macro parameter into a corresponding string

#define PRINTF(x) printf("the value of "#x" is  %d\n",x);
void functest()
{
	int a = 100;
	PRINTF(a)
}

#Instead of replacing the parameter name with 100, it retains the parameter name and turns the parameter name into a string

results of enforcement

Look at a complex macro definition

#define PRINTF(FORMAT,x)\
printf("the value of "#x" is  "FORMAT"\n",x);

#x: keep parameter name
FORMAT: Specify format

void functest()
{
	int a = 100;
	int b = 200;
	float f = 5.51;
	PRINTF("%d",a)
	PRINTF("%d",b)
	PRINTF("%f",f)
}

The function of this macro is to output in the specified format

Program running results

##

  • ##You can combine the symbols on both sides of it into one symbol. It allows the macro definition to create identifiers from separate pieces of text
#define CAT(x,y) x##y
void functest()
{
	
	int class102 = 100;
	printf("%d",CAT(class,102));
}

The program function is to combine the two identifiers into one identifier, and then macro replace it, x##y replace it with class 102, and the print result is 100

Program running results

Macro parameters with side effects

  • When a macro parameter appears more than once in the macro definition, if the parameter has side effects, you may be in danger when using this macro, resulting in unpredictable consequences. A side effect is a permanent effect that occurs when an expression is evaluated. For example:

x+1;// No side effects
x++;// With side effects

#define MAX(x,y) ((x) > (y) ? (x):(y))
void functest()
{
	
	int a = 10;
	int b = 11;  

	int ret = MAX(a++,b++);// ((a++) > (b++) ? (a++) : (b++))
	printf("%d\n", ret);//12
	printf("%d %d",a,b);//11,13
}

The two macro parameters are a + + and b + +. When they are passed, a + + will replace x and b + + will replace y. when the macro is expanded, it becomes ((a + +) > (b + +) (a + +: (b + +). Since it is a post + +, it is used in + +, 10 > 11? The latter expression is executed, so b + + will be executed twice, while a + + will only be executed once when making judgment,

Program running results

Comparison of macros and functions

Macro and function comparison:
Macros are usually used to perform simple operations. For example, find the larger of the two numbers.

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

Then why not use functions to do this? There are two reasons:

  • 1. The code used to call and return from functions may take more time than it actually takes to perform this small computational work. So macros are better than functions in terms of program size and speed.
  • 2. More importantly, the parameters of a function must be declared as a specific type. Therefore, functions can only be used on expressions of the appropriate type. On the contrary, how can this macro be applied to types such as integer, long integer, floating point, etc. that can be compared with >. Macros are type independent.

In order to facilitate you to recognize the difference between macros and functions, here is an example. Please see the following code

//From the following program, macros are really better than functions because they are written more succinctly
#define MAX(a, b) ((a)>(b)?(a):(b))

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

int main() 
{
	//Example 1:
	printf("max = %d ",Max(1, 3));//function
	printf("max = %d",MAX(1,3));//macro
	//Example 2:
	printf("max = %f ",Max(1.5, 3.6));//function 
	printf("max = %f",MAX(1.5,3.6));//macro
	return 0;
}

The first example shows that macros are better than functions. The good thing is that they are concise enough. Even a simple calculation may take 1 millisecond, but if a function is used, the call and return of the function will take up time. This process will take 2 seconds, while it also takes only 1 second to calculate the value of max and the macro definition with parameters (from the perspective of assembly code)
However, this is not the case from example 2. The macro will be replaced with text during preprocessing. It is still a floating-point number, but the Max function is different. Its two parameter types are int. in the past, the floating-point data will lose decimals and become 1 and 3. Even if the returned result changes

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

  1. Each time a macro is used, a copy of the code defined by the macro will be inserted into the program. Unless the macro is short, the length of the program may be greatly increased.
  2. Macros cannot be debugged.
  3. Macros are not rigorous enough because they are type independent.
  4. Macros may cause operator priority problems, resulting in error prone procedures.

Explanation:
1. The macro will greatly increase the length of the program? When using the macro, the macro will be replaced with text in the preprocessing stage, and the length of the code can be imagined
2. Macros can't be debugged? The preprocessing stage is arranged before the program execution. The code will be inserted into the program only after the replacement work completed in the preprocessing stage. When we debug, we actually use the compiled code, so macros can't be debugged
3. Macros are not rigorous enough because they are type independent? As explained in the previous example
4. Macros may cause operator priority problems? SQUARE(x) x * x is not considered as a whole

Sometimes macros can do things that functions can't do. For example, macro parameters can have types, but functions can't.

//num: you need to open up several type s of space
//Type: type
#define Malloc(num,type)  (type*)malloc(num * sizeof(type));
//After preprocessing replacement: (int *)malloc(10 * sizeof(int));
int main()
{
	//Use macro definition to open up 40 bytes of space
	int *p = Malloc(10,int);
	return 0;
}

Differences between macros and functions

Naming convention

All macro names should be capitalized, and function names should not be capitalized

Preprocessing instruction #undef

Function: this instruction is used to remove a macro definition.

#define MAX 100
int main()
{
	printf("%d\n",MAX);//MAX is replaced with 100 and prints normally
	#undef MAX / / cancel the macro definition of MAX
	printf("%d\n",MAX);//err,MAX undefined
	return 0;
}

Conditional compilation

Conditional compilation

  • When compiling a program, it is very convenient for us to compile or give up a statement (a group of statements) because we have conditional compilation instructions.

For example: debugging code, it's a pity to delete it and keep it, so we can compile it selectively

#include <stdio.h> 
#define __PRINTR__
int main() 
{ 
	 int i = 0; 
	 int arr[10] = {0}; 
	 for(i=0; i<10; i++) 
	 { 
		 arr[i] = i; 
		 #ifdef __PRINTR__
		 printf("%d\n", arr[i]);//To see if the array assignment is successful. 
		 #endif //__DEBUG__ 
	 } 

 return 0; 
}

#ifdef __PRINTR__ This preprocessing instruction means that if defined__ PRINTR__ This symbol is then used for printf("%d\n", arr[i]); This line of code is compiled. Conditional compilation is often used to prevent repeated inclusion of header files

Common conditional compilation instructions:

1,

1. 
#if constant expression
 //... 
#endif 
//Constant expressions are evaluated by the preprocessor.
For example:
#define __DEBUG__ 1 
#if __DEBUG__ 
 //.. 
#endif 

2. Conditional compilation of multiple branches

#if constant expression
 //... 
#elif constant expression
 //... 
#else 
 //... 
#endif


//Usage scenario
void functest()
{
	 #if 1
		printf("1\n");
	#elif 2
		printf("2\n");
	#else
		printf("3\n");
	#endif
}

Conditional compilation of multiple branches if a condition is true, only the following line of statements of the condition will be compiled, and statements of other branches will not be compiled. Here you can see that only the first statement will be compiled, and the following statements will become gray

Judge whether three writing methods are defined

3.
//Judge whether symbol is defined
#if defined(symbol) 
//Statement to compile
#endif 

//Judge whether symbol is defined
#ifdef symbol 
//Statement to compile
#endif 

//Judge whether symbol is defined
#if !defined(symbol) 
//Statement to compile
#endif 

//Judge whether symbol is defined
#ifndef symbol
//Statement to compile
#endif 

Read the conditional statements defined by macros in various forms. Readers can use whichever is suitable for them

Nested instruction

4.
#if defined(OS_UNIX) 
	 #ifdef OPTION1 
		 unix_version_option1(); 
 	 #endif / / end the condition judgment of OPTION1
 
     #ifdef OPTION2 
 	  	unix_version_option2(); 
 	 #endif / / end the judgment of OPTION2
 
#elif defined(OS_MSDOS) 
 	 #ifdef OPTION2 
 		msdos_version_option2(); 
	 #endif / / end the judgment of OPTION2
	 
#endif / / end the whole condition judgment

Statements similar to if else

File contains

We already know that the #include directive enables another file to be compiled. Just as it actually appears in the #include directive. The replacement is simple: the preprocessor first deletes the instruction and replaces it with the contents of the containing file. If such a source file is included 10 times, it will actually be compiled 10 times.
If you want to include a header file only once for your own needs, repeated inclusion of multiple header files will cause too much duplicate code, because the preprocessing stage will be replaced. Using conditional compilation is a good method. In the linux environment, you can view the expansion form of the repeatedly included files in detail

Suppose programmer A writes two copies of code, which both programmers B and C need to use. Finally, programmer D will be included twice when splicing the two modules of B and C

Solution: conditional compilation

#ifndef __TEST_H__  // Determine whether to include header files
#define __TEST_H__ 
//Include header file
#include"comm.h"
#endif //__TEST_H__

Understanding #ifndef TEST_H. If not defined__ TEST_H__ If the judgment condition is true, execute #define TEST_H. Definition__ TEST_H__ , Finally, include the header file

A modern way of writing

#pragma once
#include"comm.h"

Summary:
ifndef/define/endif in the header file is to prevent repeated inclusion of header files

How header files are included:

There are two search strategies. First, search in the directory where the source file is located. If the header file is not found, the compiler looks for the header file in the standard location like looking for the library function header file.
If not found, a compilation error will be prompted

Library file contains

#include <filename.h>

Local file contains

#include "filename.h"

Library files can also be included in the form of ""? The answer is yes, yes. However, the efficiency of searching is lower. Of course, it is not easy to distinguish whether it is a library file or a local file

finish

Keywords: C

Added by abalfazl on Fri, 01 Oct 2021 07:54:14 +0300