content
The translation environment and execution environment of the program
Detailed compilation + linking
The compilation itself is also divided into several stages
Macro arguments with side effects
Common conditional compilation directives
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 putSQUARE ( 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.