C language--programming environment and preprocessing (macro)

Table of contents

Preface

Highlights of this chapter:

 1. Program translation environment and execution environment

2. Detailed explanation of compilation + linking

2.1 Translation environment editor

2.2 Compilation itself is also divided into several stages

2.3 Operating environment

3. Detailed explanation of preprocessing

3.1 Predefined symbols

3.2 #define

3.2.1 #define define identifier

3.2.2 #define define macro

2.2.3 #define replacement rules

3.2.4 # and ##

 3.2.5 Macro parameters with side effects

3.2.6 Comparison between macros and functions

3.2.7 Naming Convention

3.2.8 Simplifying malloc

3.3 #undef

3.4 Command line definition

3.5 Conditional compilation

3.6 File inclusion

3.6.1 How header files are included

 3.6.2 Nested file inclusion


Preface

        This chapter is the last section of the C language. After studying this chapter, we will know how to turn the written code into an executable program. This is a very important chapter, so let us enter this chapter together.


Highlights of this chapter:

  • Program translation environment
  • program execution environment
  • Detailed explanation: Compilation + linking of C language programs
  • Introduction to predefined symbols
  • Preprocessing directive #define
  • Comparison of macros and functions
  • Introduction to preprocessing operators # and ##
  • Command definition
  • Preprocessing directive #include
  • Preprocessing directive #undef
  • conditional compilation

 1. Program translation environment and execution environment

In any implementation of ANSI C, there are two different environments.

The first type 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

  • Each source file that makes up a program is individually 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 that are used by the program, and it can search the programmer's personal program library and link the functions he needs into the program.

2.2 Compilation itself is also divided into several stages

These operations are performed in a Linux environment to assist us in understanding these processes

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.


2. Compilation option gcc -S test.c
Stop after compilation is completed, and the results are saved in test.s.


3. Assemble gcc -c test.c
Stop after the assembly is completed, and the result is saved in test.o.

4. Link gcc test.o -o test

Link object files and link libraries to generate executable programs (binary programs)

5. Summary

6. Symbol table

        The main purpose of the symbol table is to provide the compiler, interpreter or debugger with information about the symbols in the program for semantic analysis, type checking, memory allocation and symbol resolution. It can also be used for compiler-related tasks such as error detection, code optimization, and code generation.

        For example: During the connection phase, the symbol table will be merged. If the symbol is not found, a link error will be reported.


2.3 Operating 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 the main function is called.
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,否则未定义

example:

#include<stdio.h>
int main()
{
    printf("%s\n", __FILE__);    
    printf("%d\n", __LINE__);
    printf("%s\n", __DATE__);
    printf("%s\n", __TIME__);
      
    return 0;
}


3.2 #define


3.2.1 #define define identifier

Variables defined by #define have been replaced during the preprocessing stage.

语法:
#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__ )

Question:
When defining an identifier, should you add ; at the end? ?
For example:

#define MAX 1000;
#define MAX 1000

It is recommended not to add ; as this may easily cause problems.
For example, the following scene:

  This will cause a grammatical error.

if(condition)
    max = MAX;
else
    max = 0;

3.2.2 #define define macro

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

define macro.

Here's how to declare a macro:

#define name( parament-list ) stuff

where parament-list is a comma-separated list of symbols that may appear in stuff.
Note:
The left bracket of the parameter list must be immediately adjacent to name.
If any whitespace exists in between, the parameter list will be interpreted as part of stuff.

For example:

#define SQUARE( x ) x * x

This macro accepts 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?
When replacing the text, the parameter x is replaced with a + 1, so this statement actually becomes:

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 55 appears.
The solution to this problem is to add the expression on both sides of the macro definition Just add a pair of brackets .

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

Tip:
        All macro definitions used to evaluate numeric expressions should be bracketed in this way to avoid errors due to operators in the parameters when using macros. or unpredictable interactions between adjacent operators.


2.2.3 #define replacement rules

When expanding #define definition symbols and macros in a program, several steps are involved.
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.

example:

#define M 100

#define ADD(x,y)  ((x)+(y))

int main()
{
	int a = 10;
	int b = 20;
	int c = 4*ADD(M, b);
	//int c = 4 * a + b;
	printf("%d\n", c);
	printf("Master\n");
	return 0;
}

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, scan the resulting file again to see if it contains any symbols defined by #define. If so, repeat the above process.
Note:
1. Symbols defined by other #define 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.


3.2.4 # and ##

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

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

Is the output here
hello world?
The answer is a resounding yes.
We found that strings have the characteristics of automatic connection.
        1. 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:

#define PRINT(FORMAT, VALUE)\
printf("the value of " #VALUE "is "FORMAT "\n", VALUE);

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

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

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.

PRINT("%d", i + 3);//产生了什么效果?
#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, you may be in danger when using this macro, leading 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);

What is the output?                                                               What is the result after preprocessor processing:

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

        At this time, for the first time x++, the current value is 5, but after ++ it becomes 6; for y++, the current value is 8, but after ++ it becomes 9. After the expression is executed y++ again, z gets the value of y, 9, but y still needs ++, and the value becomes 10.

So the output is:

x=6 y=10 z=9

3.2.6 Comparison between macros and functions

The macro is 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.
So 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 and other types that can be used for comparison?
Macros are type-independent.
Disadvantages of macros: Of course, macros also have disadvantages compared to functions:
1. Every time you use a macro , a macro definition code will be inserted into the program. Unless the macro is relatively short, it may significantly increase the length of the program.
2. Macros cannot be debugged.
3. Macros are not rigorous enough because they are type-independent.
4. Macros may cause operator precedence problems, making the program prone to errors.

Macros can sometimes do things that functions cannot. For example: macro parameters can appear of type , 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

Attributes #definedefine macro function
code length Each time is used, the macro code will be inserted into the program. Except for very
small macros, the length of the program will increase significantly
The function code only appears in one place; every time
this function is used, the same code in that
place is called
Execution speed Faster (justcalculate)

There is the additional overhead of function calls and returns, so it is relatively slower.

(1. Function call, parameter transfer, stack frame creation. 2. Calculation. 3. Function return)

Operator priority Macro parameters are evaluated in the context of all surrounding expressions,
Unless parentheses are used, the precedence of adjacent operators may occur< a i=2> has unpredictable consequences, so it is recommended that macros be written with more brackets .

The function parameter is evaluated
only once when the function is called, and its result value is passed to the function
. The result of an expression's evaluation is easier to predict.
Parameters with side effects The parameter may be substituted at multiple locations in the macro body, so parameter evaluation with side effects
may produce unpredictable results.
Function parameters are only evaluated once
when passing parameters, and the result is easier to control.
Parameter Type The parameters of the macro have nothing to do with the type. As long as the operation on the parameters is legal,
it can be used for any parameter type.
The parameters of the function are related to the type. For example
if the parameters are of different types, different
functions are needed, even if they perform tasks is
different.
debug Macros are inconvenient to debug Functions can be debugged statement by statement
recursion Macros cannot be recursive Functions can be recursive

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: Use all capital letters for macro names, but not all capital letters for function names


3.2.8 Simplifying malloc

Like this, defining malloc with a macro can make it easier.

#define MALLOC(num, type)   (type*)malloc(num*sizeof(type))

int main()
{
	//int*p = (int*)malloc(10 * sizeof(int));
	//malloc(10, int);
	//malloc(5, double);
	int*p = MALLOC(10, int);
	if (p == NULL)
	{
		//...
	}

	return 0;
}

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 if the memory of another machine is large, we need an array that can be large. .)

#include <stdio.h>
int main()
{
	int array[SZ];
	int i = 0;
	for (i = 0; i < SZ; i++)
	{
		array[i] = i;
	}
	for (i = 0; i < SZ; i++)
	{
		printf("%d ", array[i]);
	}
	printf("\n");
	return 0;
}

 Compilation instructions:

//linux 环境演示
gcc -D SZ=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:Debugging code is a pity to delete, but keeping it is in the way, so we can selectively compile it.

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

  This code is only compiled when the DEBUG macro is defined.

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. Just like where it actually appears in the #include directive.

This replacement method is very simple:
        The preprocessor first deletes this instruction 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 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 a standard location just like it searches for library function header files.
If it cannot be found, a compilation error will be prompted.

The path to the standard header file in the Linux environment:

/usr/include

The path to the standard header file of the VS environment:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

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

  • 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, yes.
But the search efficiency is lower. Of course, it is not easy to distinguish whether it is a library file or a local file.


 3.6.2 Nested file inclusion

If this 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 module and test2 module.
In this way, two copies of comm.h will appear 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 repeated references to header files.
 

This chapter is over! Complete explanation of C language knowledge!

Guess you like

Origin blog.csdn.net/2301_76618602/article/details/133927110