C language programming environment and preprocessing

1. Program translation environment and execution environment

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

2. Detailed explanation of compilation + linking

2.1 Translation environment

Insert image description here
Each source file that makes up a program is converted into object code through the compilation process.
Each object file is bundled together by the linker to form a single and complete executable program.
The linker will also introduce any functions in the standard C function library used by the program, and it can search the programmer's personal
library to The functions it requires are also linked into the program.
2.2 The compilation itself is also divided into several stages: ‘
Look at the code:
sum.c

int g_val = 2016;
void print(const char *str)
{
    
    
 printf("%s\n", str);
}

test.c

#include <stdio.h>
int main()
{
    
    
 extern void print(char *str);
 extern int g_val;
 printf("%d\n", g_val);
 print("hello bit.\n");
 return 0;
}

Insert image description here
How can I see what happened at each step during compilation?
test.c

#include <stdio.h>
int main()
{
    
    
 int i = 0;
 for(i=0; i<10; i++)
 {
    
    
 printf("%d ", i);
 }
 return 0;
}

1. Preprocessing option gcc -E test.c -o test.i
Stop after preprocessing is completed, and the results generated after preprocessing are placed in test.i in the file.
Compilation option gcc -S test.c
2. Stop after the compilation is completed, and the results are saved in test.s.
Assembly gcc -c test.c
3. Stop after the assembly is completed, and the result is saved in test.o.
2.3 Running environment
Program execution process:
1. The program must be loaded into memory. In an environment with an operating system: This is usually done by the operating system. In a stand-alone environment, the loading of the program must be arranged manually, or it may be done by placing executable code into read-only memory.
2. The execution of the program begins. Then call the main function
3. Start executing the program code. At this time, the program will use a runtime stack to store the local variables and return address of the function. Programs can also use static memory. Variables stored in static memory retain their values ​​throughout the execution of the program.
4. Terminate the program. Terminate the main function normally; it may also terminate unexpectedly. ,

3. Detailed explanation of preprocessing

3.1 Predefined symbols

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

These predefined symbols are built into the language.
For example:

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

3.2 #define

3.2.1 #define define identifier

语法:
 #define name stuff

for 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__ ) 

3.2.2 #define define macro

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。

The following is how to declare a macro:
#define name( parament-list ) stuff
where parament-list is a comma-separated Symbol tables that may appear in stuff.
Note:
The left bracket of the parameter list must be immediately adjacent to name.
If any whitespace exists between the two, the parameter list will be interpreted as part of stuff.
For example:

#define SQUARE( x ) x * x

This macro receives a parameter x .
If after the above declaration, you put

SQUARE( 5 );

placed in the program, the preprocessor will replace the above expression with the following 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?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );

This makes it clear that the expressions produced by substitution are not evaluated in the expected order.
Add two parentheses to the macro definition, and this problem can be easily solved:

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

This preprocessing produces the expected effect:

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

Here is another macro definition:

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

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

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

What value will this print?
warning:
It seems that 100 is printed, but in fact, 55 is printed.
We found that after replacement: < /span>

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

The multiplication operation precedes the addition of the macro definition, so the problem 55
occurs. The solution is to add a pair of parentheses on both sides of the macro definition expression a>

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

hint:

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中
的操作符或邻近操作符之间不可预料的相互作用。

2.2.3 #define replacement rules
'When extending #define definition symbols and macros in a program, several steps are involved.

  1. When calling a macro, the parameters are first checked to see if they contain any symbols defined by #define. If so, they are first
    replaced.
  2. The replacement text is then inserted into the program at the location 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 so, repeat the above process. Note:

  4. Symbols defined by other #define can appear in macro parameters and #define definitions. But for macros, recursion cannot occur.
  5. When the preprocessor searches for #define symbols, the contents of string constants are not searched.

3.2.4 # and ##

How to insert parameters into a string?
First let’s look at this code:

char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);

Is the output here
hello bit?
The answer is a resounding yes.
We found that strings have the characteristics of automatic connection.

  1. So can we write such code? :
#define PRINT(FORMAT, VALUE)\
 printf("the value is "FORMAT"\n", VALUE);
...
PRINT("%d", 10);

Here, the string can be placed in the string only when the string is used as a macro parameter.

  1. Another trick is:
    Use # to turn a macro parameter into the corresponding string.
    For example
int i = 10;
#define PRINT(FORMAT, VALUE)\
 printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);//产生了什么效果?

#VALUE in the code will be processed by the preprocessor as:
"VALUE" .
The final output result should be:

the value of i+3 is 13

The role of ##
## can combine the symbols on both sides of it into one symbol.
It allows macro definitions to create identifiers from separate text fragments.

#define ADD_TO_SUM(num, value) \
 sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.

Note:
Such a connection must produce a valid identifier. Otherwise the result is undefined.
3.2.5 Macro parameters with side effects
When a macro parameter appears more than once in the definition of a macro, if the parameter has side effects, then you are using This macro may
be dangerous and lead to unpredictable consequences. Side effects are permanent effects that occur when an expression is evaluated.
For example

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

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

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

Here we need to know what the result is after preprocessor processing:

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

So the output is:

x=6 y=10 z=9

3.2.6 Comparison between macros and functions
Macros are usually used to perform simple operations.
For example, find the larger of two numbers.

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

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

  1. The code used to call and return from the function may take more time than it takes to actually perform this small computational work.
    Therefore, macros are better than functions in terms of program size and speed.
  2. More importantly, the parameters of the function must be declared as specific types.
    So the function can only be used on expressions of the appropriate type. On the contrary, how can this macro be applied to integers, long integers, floating point types, etc. that can be compared with
    and>?
    Macros are type-independent.
    Disadvantages of macros: Of course, macros also have disadvantages compared to functions:
    1. Every time a macro is used, a copy of the macro definition code will be inserted. into the program. Unless the macro is relatively short, it may significantly increase the length of the program.
  3. Macros cannot be debugged.
  4. Macros are not rigorous enough because they are type-independent.
  5. Macros may cause operator precedence problems, making programs prone to errors.

Macros can sometimes do things that functions cannot. For example: macro parameters can have types, but functions cannot.

#define MALLOC(num, type)\
 (type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

A comparison of macros and functions

Insert image description here
3.2.7 Naming Convention
Generally speaking, the syntax for using function macros is very similar. So language itself cannot help us distinguish between the two.
Then one of our usual habits is:

把宏名全部大写
函数名不要全部大写

3.3 ​​#undef
This command is used to remove a macro definition

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

3.4 Command line definition
Many C compilers provide the ability to define symbols 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. (Suppose
an array of a certain length is declared in a program. If the machine memory is limited, we need a very small array, but another
machine memory is uppercase, we need an array that can be uppercase.)

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

Compilation instructions:

//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c

3.5 Conditional compilation
When compiling a program, it is very convenient if we want to compile or abandon a statement (a group of statements). Because we have conditional compilation directives.
For 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;
}

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

3.6 File inclusion
We already know that the #include directive can cause another file to be compiled. This replacement works simply as it does in the place where the #include directive actually appears:
The preprocessor first removes this directive and replaces it with the contents of the included file.
If such a source file is included 10 times, it will actually be compiled 10 times.
3.6.1 How header files are included:
Local file inclusion

#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 at the specified location just like it searches for library function header files.
.
If it cannot be found, a compilation error will be prompted.
Library file contains

#include <filename.h>

Search for header files directly in the standard path. If not found, a compilation error will be prompted.
Does this mean that library files can also be included in the form of ""?
The answer is yes, you can.
However, the search efficiency is lower. Of course, it is not easy to distinguish whether it is a library file or a local file.

4. Other preprocessing instructions

#error
#pragma
#line
....................
//还有很多不做介绍了
//可以参考《C语言深度解剖》学习

End of this chapter…

Guess you like

Origin blog.csdn.net/HD_13/article/details/133950021