Problems that beginners tend to ignore about preprocessing instructions

1. The translation environment and execution environment of the program

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

Finally, the executable program is generated by the linker together

The process of program execution: 1. The program must be loaded into memory. In an environment with an operating system: Generally this is done by the operating system. In a standalone environment, program loading 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. Begin executing the program code. At this time, the program will use a runtime stack (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. Normal termination of the main function; unexpected termination is also possible.

2. Preprocessing instructions

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

2.1 The role of #define

2.1.1 #define definition identifier

#define name stuff
#define MAX 1000
#define datatype int          //为 datatype这个关键字,创建一个简短的名字
#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%d\n", \
                          __FILE__,__LINE__,       \
                          __DATE__,__TIME__ )

When defining an identifier, it is best not to add at the end;

#define MAX 66;
int main()
{
  if(condition)
  max = MAX;
  else
  max = 0;
}

For example, the above code will generate a syntax error.

2.1.2#define define macro

The efine mechanism includes a provision that allows parameters to be substituted into the text, this implementation is usually called a macro (macro) or define macro (define macro).

#define name( parament-list ) /*注意符号表不能与宏之间有空格等 */stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
#define square(x)  x*x
int a=3;
printf("%d",square(a+1));
//得出的结果是7而非16,这是因为宏是进行直接替换的

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

2.1.3#define substitution rules

There are several steps involved when expanding #defines to define symbols and macros in a program. 1. When calling a macro, the parameters are first checked to see if they contain any symbols defined by #define. If yes, they are replaced first. 2. The replacement text is then inserted in the program in place 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 yes, repeat the above processing.

2.1.4 Attention

1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。 2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

2.2#define把字符串插入到字符串中

我们发现字符串是有自动连接的特点的。

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

PRINT("%d", 10);

这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中

另外一个技巧是: 使用 # ,把一个宏参数变成对应的字符串。 比如:

int i = 10;
#define PRINT(FORMAT, VALUE)\
 printf("the value of " #VALUE "is " FORMAT "\n", VALUE);
...
PRINT("%d", i+3);//代码中的 #VALUE 会预处理器处理为:"VALUE"

2.3 #和##

#define ADD_TO_SUM(num, value) \
 sum##num += value;

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

2.3带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

2.4#undef

这条指令用于移除一个宏定义

3.小结

3.1宏和函数对比

有时候为为什么使用宏 1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹。 2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以 用于>来比较的类型。

宏的缺点:当然和函数相比宏也有劣势的地方: 1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。 2. 宏是没法调试的 3. 宏由于类型无关,也就不够严谨。 4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

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

//使用
int *p=MALLOC(10, int);//类型作为参数
//预处理器替换之后:
//(int *)malloc(10 * sizeof(int));

#define定义宏和函数 对比总结

1.代 码 长 度 :

每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长 函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码

2.执 行 速 度 : 更快 存在函数的调用和返回的额外开 销,所以相对慢一些

3. 作 符 优 先 级 : 宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。 函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更容易预 测。

4.带 有 副 作 用 的 参 数: 参数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果。 函数参只在传参的时候求值一 次,结果更容易控制。

5.参 数 类 型: 宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。 函数的参数是与类型有关的,如 果参数的类型不同,就需要不同 的函数,即使他们执行的任务是 不同的。

6.调 试:

宏是不方便调试的 函数是可以逐语句调试的

7.递 归 : 宏是不能递归的 函数是可以递归的

3.2宏和函数的命名规则

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

3. 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)

4.条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件 编译指令。 比如说: 调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

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

5. 文件包含

这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。

5.1本地文件包含

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。 如果找不到就提示编译错误。

5.2库文件包含

找头文件直接去标准路径下去查找,如果找不到就提示编译错误

这样是不是可以说,对于库文件也可以使用 “” 的形式包含? 这当然是肯定可以的。 但是这样做

1.查找的效率就低些

2.也不容易区分是库文件还是本地文件了。

5.3头文件的重复包含

comm.h和comm.c是公共模块。 test1.h和test1.c使用了公共模块。 test2.h和test2.c使用了公共模块。 test.h和test.c使用了test1模块和test2模块。 这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //啥也不写

Guess you like

Origin blog.csdn.net/m0_74234485/article/details/129093929