[C language advanced] program environment and preprocessing

content

The translation environment and execution environment of the program   

Detailed compilation + linking

Translation environment​

The compilation itself is also divided into several stages

Operating environment

 Detailed preprocessing

 predefined symbols

 #define

 #define define identifier

 #define define macro

 #define Substitution Rules

 #and##

Macro arguments with side effects

Macro and function comparison

naming convention

#undef

Conditional compilation

Common conditional compilation directives

file contains

How header files are included

Nested file contains


The translation environment and execution environment of the program   

In any implementation of ANSI C, there are two distinct environments.
The first is a translation environment, in which source code is translated into executable machine instructions.
The second is the execution environment, which is used to actually execute the code.

Detailed compilation + linking

Translation environment

Each source file that composes a program is converted into object code (object code) separately through the process of compilation (compilation is divided into pre-processing (pre-compilation), compilation and assembly).
Each object file is bundled together by the linker to form a single and complete executable program.
The linker also introduces any functions in the standard C function library that are used by the program, and it can search the programmer's personal library and link the functions it needs into the program.

The compilation itself is also divided into several stages

Operating environment

The process of program execution:

1. The program must be loaded into memory. In an environment with an operating system: Usually this is done by the operating system. In a stand-alone environment, the loading of programs must be arranged manually, possibly by placing executable code into read-only memory.
2. The execution of the program starts. Then call the main function.
3. Start executing the program code. At this point the program will use a runtime stack (stack) to store the function's local variables and return addresses. Programs can also use static memory. Variables stored in static memory retain their values ​​throughout the execution of the program.
4. Terminate the program. Terminates the main function normally; it may also terminate unexpectedly.

 Detailed preprocessing

 predefined symbols

__FILE__ // the source file to compile
__LINE__ // the current line number of the
file __DATE__ // the date the
file was compiled __TIME__ // the time the file was compiled
__STDC__ // 1 if the compiler follows ANSI C, otherwise undefined (Not supported by VS2019)
These predefined symbols are all built into the language.

For example:
printf ( "file:%s line:%d\n" , __FILE__ , __LINE__);

 #define

 #define define identifier

Syntax:
#define name stuff
For example:
#define MAX 1000
#define reg register // Create a short name for the register keyword
#define do_forever for(;;) // Replace one with a more vivid notation Implement
#define CASE break;case // Automatically write break when writing a case statement.
// If the defined stuff is too long, it can be divided into several lines, except for the last line, each line is followed by a backslash (continuation character).
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ , \
                          __DATE__,__TIME__ )  
Note: When #define is defined, the following It is best not to add ";", it is error-prone.
3.2.2 #define Defining macros
The #define mechanism includes a provision that allows parameter substitution into text, an implementation commonly referred to as a macro or define macro.
 

 #define define macro

The #define mechanism includes a provision that allows parameter substitution into text, an implementation commonly referred to as a macro or define macro.

Here is how the macro is declared:

#define name( parament-list ) stuff

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

Note :

The opening parenthesis of the parameter list must be immediately adjacent to name.
If there is any white space in between, the parameter list is interpreted as part of stuff.

Such as:

#define SQUARE( x ) x * x

This macro takes one parameter x.
If after the above declaration, you put

SQUARE ( 5 );

Placed in a program, the preprocessor replaces the above expression with this 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 by a + 1, so the statement actually becomes:

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

This makes it clear that the expressions resulting from the substitution are not evaluated in the expected order.

This problem is easily solved by adding two parentheses to the macro definition:

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

This preprocessing produces the expected effect:

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

Here's another macro definition:

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

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

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

What value will this print?
Warning:
It looks like it prints 100, but it actually prints 55.
We find that after replacing:

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


The multiplication operation precedes the addition defined by the macro, so there is
 

//55 .

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

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

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

 #define Substitution Rules

There are several steps involved when expanding #define to define symbols and macros in a program.
1. When calling the macro, the parameters are first checked 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 program in place of the original text. For macros, parameter names are replaced by their values.
3. Finally, scan the resulting file again to see if it contains any symbols defined by #define. If so, repeat the above process.

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

 # and ##

role

Use # to turn a macro parameter into the corresponding string.
for example:

#include <stdio.h>
#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE " is "FORMAT "\n", VALUE);

int main()
{
	int i = 10;

	PRINT("%d", i + 3);//产生了什么效果?
	return 0;
}

replace later code

#include <stdio.h>

int main()
{
	int i = 10;

	printf("the value of i + 3 is %d \n", i+3);
	return 0;
}

The role of ##

## can combine the symbols on either side of it into one symbol.

It allows macro definitions to create identifiers from separate pieces of text.

#include <stdio.h>
#define ADD_TO_SUM(num, value) sum##num += value;

int main()
{
	int sum5 = 20;
	ADD_TO_SUM(5, 10);//作用是:给sum5增加10

	return 0;
}

replace later code

#include <stdio.h>


int main()
{
	int sum5 = 20;
	sum5 += 10;
	return 0;
}

Macro arguments with side effects

When a macro parameter appears more than once in the macro definition, if the parameter has side effects, then you may be dangerous when using the macro, leading to unpredictable consequences. A side effect is a permanent effect that occurs when an expression is evaluated.
E.g:

x + 1 ; // 不带副作用
x ++ ; // 带有副作用

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

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

int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z); // 输出的结果是什么?
	return 0;
}

Here we have to know what the result of the preprocessor processing is:

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

So the output is:

x = 6 y = 10 z = 9

Macro and function comparison

Attributes #define define macro 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
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 of function call and return
, so it is relatively slow
operator precedence Macro arguments are evaluated in the context of all surrounding expressions.
Unless parentheses are added, the precedence of adjacent operators may have
unpredictable consequences, so it is recommended that macros be written with more parentheses
.
A function parameter is evaluated only
once when the function is called, and its resulting value is passed to the function
. Expression evaluation results are more
predictable.
Parameters with side effects Arguments may be substituted in multiple places in the macro body, so side-effects
of argument evaluation may produce unpredictable results.
Function parameters are only evaluated
once when they are passed, and the result is easier to control.
Parameter Type The parameters of a macro are type-independent, and
can be used with any parameter type as long as the operation on the parameter is legal.
Function parameters are type-dependent, and
if the parameters are of different types, different
functions are required, even if the tasks they perform are
different.
debugging Macros are inconvenient to debug Functions can be debugged statement by statement
recursion Macros cannot be recursive Functions can be recursive

naming convention

In general, the syntax for using macros for functions is similar. So language itself cannot help us distinguish between the two.
Then one of our usual habit is:
capitalize macro names
and do not capitalize function names.

#undef

This directive is used to remove a macro definition.

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

Conditional compilation

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

 For example: debug code, delete it is a pity, keep it in the way, so we can selectively compile.

#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;
}

Common conditional compilation directives

1.

#if 常量表达式
//...
#endif
// 常量表达式由预处理器求值。

Such as:

#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

2. Conditional compilation of multiple branches

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

3. Determine if it is defined

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

4. Nested Directives
 

#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

file contains

We already know that the #include directive can cause another file to be compiled. as if it actually appears in the place of the #include directive.

 The way this replacement works 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, it is actually compiled 10 times.

How header files are included

local file contains

#include "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 the library function header file.
If it is not found, it will prompt a compilation error.

Path to standard header files for VS (2019) environment: 

C : \Program Files ( x86 ) \Microsoft Visual Studio 12.0 \VC\include

Pay attention to find it according to your own installation path.

The library file contains
#include <filename.h>
to find the header file and go directly to the standard path to find it. If it is not found, it will prompt a compilation error.
Is it possible to say that library files can also be included in the form of ""?

The answer is yes .
However, the search efficiency is lower in this way, and of course, it is not easy to distinguish whether it is a library file or a local file .
 

Nested file contains

If such a scenario occurs:

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 and test2 modules.

In this way, there will be two copies of comm.h in the final program. This results in 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

This avoids duplication of header files.

Guess you like

Origin blog.csdn.net/qq_54880517/article/details/124380176