Bootstrap

预处理指令

1.预定义符号

预定义符号是在预处理阶段处理的。

 
1.__FILE__ // 进⾏编译的源⽂件
2.__LINE__ // ⽂件当前的⾏号
3.__DATE__ // ⽂件被编译的⽇期
4.__TIME__ // ⽂件被编译的时间
5.__STDC__ // 如果编译器遵循 ANSI C ,其值为 1 ,否则未定义
由于vs2022未完全使用ANSI C所以__STDC__未定义

2.#define 定义常量

# define name stuff
我们在前面定义宏的时候好像都没有加上; ,这是为什么呢?
因为宏定义在展开的时候回直接替换原来位置的内容,所以如果定义的时候加上分号会容易导致问题
由于if没加代码块,它只能直接匹配一段代码,而这里有两个分号,也就是两个语句,导致后面的else不知道跟谁匹配。

3.#define定义宏

define定义宏和定义常量的区别就是定义宏有参数,这种实现通常称为宏(macro)或定义宏

(define macro)。

宏的申明方式:

# define name( parament-list ) stuff
注意:()必须与name紧挨着,否则他会直接替换为后面的内容。
比如我们要写一个乘法的宏:
 
但是这样写会存在一些问题:
我们期望的是4*6得到24,可是实际写过确实9 。这也是宏替换的规则导致的,实际算的是3+1*5+1=9
结论:定义宏的时候不要吝啬括号

4.带有副作用的宏参数

带有副作用的宏参数比如++ --操作符,在参数数量超过1个时会导致问题

比如:

这里得到的是7 4 6 也是因为宏的处理是替换而不是函数的先计算再传值。

这里经过预处理后:

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

5.宏替换的规则

1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先

被替换。
这里就是在替换MAX的时候检查到里面还有一个NUM宏,先对NUM进行替换
2. 替换文本随后被插⼊到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3.最后,再次对结果⽂件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上

述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

6.宏和函数的对比

两个求最大值的方法对比下,宏的方法更好一点:

1.宏的处理只涉及到计算,而函数涉及到函数的调用、计算、函数的返回。因此函数的时间开销大一点

2.宏的替换原则并没有参数限制,而函数针对于不同的参数要写不同的版本。

函数的优势:

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

7.#和##

#运算符

#并不是#include或者#define的#

#运算符将宏的⼀个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为”字符串化“。
比如我们要写下面一段代码:
我们发现这个打印功能可以抽象出来一个宏:

## 运算符

## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。
## 被称为记号粘合 这样的连接必须产生⼀个合法的标识符。否则其结果就是未定义的
比如我们要定义一个宏来写不同类型数据求最大值得函数:

8.命名约定

由于宏和函数的相似性,所以为了区分它们两个,一般的:

宏的字母全部大写

函数一般是首字母大写

9.#undef

当我们定义的宏在某时段不用了或者需要重定义,那么这就需要把原有的定义取消掉。

这就需要用到#undef

10.命令行定义

假设我们的代码要根据不同的机器设置不同的版本,就像前面的位段一样,就要用的命令行代码

这里简单的拿一个数组大小举例:

我们并在写的时候并不直接将ARRAY_SIZE定义。而是在使用的时候给一个大小。

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

11.条件编译

条件编译顾名思义就是看情况编译,这跟if语句很像,但是它们的处理阶段不同,一个是在预处理阶段,一个是运行阶段。

比如我们在编译的时候需要有调试语句,但是不用的时候删掉再写一遍又很麻烦,所以我们选择性编译:

常见的形式:

1.

#if 常量表达式

//...

#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

12.头文件的包含

头文件有两种包含方式:

1.本地头文件包含

包含方式:

先从自己所在的路径搜索,如果没有就在存放标准库的文件中搜索。

2.库函数的头文件包含

包含方式:

直接从存放标准库的文件中搜索。

3.嵌套文件包含

像这样的多次包含是会出现的,而且编译器在预处理的时候也会真的把他们都拷过来。

如何避免呢?

条件编译。
完。
;