预编译阶段所做的事情我们清楚了,其实就是处理宏定义、条件编译、头文件包含、特殊预处理关键字这些内容,
本章的重点就是详细的讲解下“宏定义”、“条件编译”、“头文件包含”、“特殊预处理关键字”

一、编译的四过程(四阶段)

      预编译          编译           汇编
***.c ——————> ***.i ———————> ***.s ———————> ***.o ——|
                                                    |
                                                    |   链接
                                                     >————————>a.out
                                                    |
                                                    |
***.c ——————> ***.i ———————> ***.s ———————> ***.o ——|
                                                    |
                      编译器提供的其它.o(启动代码)  ——|
                                                    |
                                库(静态库、动态库)——|

二、预编译时做的五件事情

我们在第一章详细地介绍过这四个阶段,由于本章与第一阶段“预编译”有关,我们这里需要回顾“预编译”阶段

  1. 头文件包含
    .c文件中,将所有#include <***.h>展开,说白了就是将.h中的宏定义、函数声明、
    结构体类型定义等等内容复制到.c中,以供第二阶段“编译”时使用
  2. 宏替换
    预处理后,宏会被宏体所替换,宏名消失。
  3. 条件编译
    预编译时条件编译(代码选择开关),会选择哪些代码留下,哪些代码去掉,预编译后“条件编译”就消失了
  4. 处理#error#line###等一些特殊的“预编译关键字”
  5. 处理注释
    注释只是给程序员看的,cpu运行时并不需要,预编译之后,///* */所标记的注释将不复存在

三、C 预处理代码特点

所有C的预处理代码,比如#define#include#if等都是独占一行,而且结尾没有封号;

#define PI 3.14  
#include <stdio.h>

#if NUM == 500
...
#endif

因为凡是;结尾,都是由第二阶段“编译”来处理的(a.i->a.s),
而所有预编译的代码都是在预编译阶段处理的,为了以示区分,所以所有预编译的代码都不需要封号结尾


四、宏定义

1. 宏定义的用途

(1)减少重复劳动

比如,程序中有很多地方都要用到100这个数,当需要将100修改为200时,
就需要到每个引用100的地方将100改为200,这就是重复劳动。此时最好就将100定义为宏,

#define NUM 100
(2)方便阅读

我们在定义每个宏时,宏名都是有一定含义的,有意义的宏名能够帮助我们阅读和理解代码

#define PI 3.14

一看这个宏名你就知道这是圆周率,如果在程序中直接写3.14的话,很难理解这个数字的含义

(3)简化复杂表达式

简化使用调用

#define AREA(a,b) (((a)+(b))*((a)-(b)))/a*b

在代码中直接调用AREA带参宏,显然简化了代码,向这类的简化复杂表达式的宏,
在复杂C源码中非常多,由于这类宏的宏体复杂,往往也成为了很多同学阅读复杂C源码的障碍之一

(4)与条件编译配合使用
#define ARM

#ifdef ARM 
...

#endif

2. 宏的种类

宏有两种,

  1. 有宏体宏:#define X86
  2. 无宏体宏:#define NUM 100,#define AREA(r) (r)*(r)*3.14
(1)无宏体宏

只有宏名、没有宏体 #define 宏名
预编译完后,由于这个宏没有宏体,所以宏直接替换为空,空就是啥也没有

这种宏有作用吗?有作用,后面讲到条件编译时再介绍

(2)有宏体宏分为两种
  1. 无参宏:没有参数,#define NUM 100
  2. 有参宏:有参数,#define AREA(r) (r)*(r)*3.14

五、无参宏

1. 定义形式

#define 宏名 宏体

2. 举例

 #define YES  1
 #define NO   0
 #define PI   3.1415926
 #define OUT  printf(“Hello,World”);

预处理后,宏名会被替换为宏体


六、带参宏

1. 定义形式

#define 宏名(参数表) 宏体

2. 举例

#define  S(a,b)  a*b*10

int main(void)
{
    int va;
    va = S(3,2); //3对应a,2对应b
    printf("va = %d\n", va);
    return 0;
}

预编译处理时,将宏体中的ab,使用参数中的32来替换

va = S(a, b) ——————> va = 3*2*10

3. 带参宏需要注意之处

1)宏名和参数列表之间不能有空格
#define  S(a, b)  a*b*10

如果S(a, b)之间有空格,宏名变为了S,宏体变为了(a, b) a*b*10,含义发生了变化

2)写带参宏的时,不要吝啬括号

其实这个带参宏是有缺点的,如果参数写成如下形式的话,替换后结果可能就完全背离了你的本意

    a    b
 S(x+1, y+2) ——————————————> x+1*y+2*10

对于预编译器来说,它再处理宏定义时,它并不知道你的使用意图是什么,它只会忠实的进行替换工作,
但是替换之后是否能够达到你要的效果,这个就不一定了

怎么解决?为了避免这种情况,大家在定义带参宏时不要吝啬括号

#define  S(a,b)  ((a)*(b)*10)   //为了保险起见,对整个宏体也要加()。
S(x+1, y+2) ——————————————> ((x+1)*(y+2)*10)

疑问:为什么要对整个宏体加括号?

#define ETH_MMC_BASE          (ETH_BASE + 0x0100)
#define ETH_PTP_BASE          (ETH_BASE + 0x0700)
#define ETH_DMA_BASE          (ETH_BASE + 0x1000)

*ETH_MMC_BASE = 0x67667732;

如果不加括号的话,*ETH_MMC_BASE = 0x67667732; 就变为了 *ETH_BASE + 0x0100 = 0x67667732
所以,凡事宏体是一个表达式的情况,我们都要求对整个宏体加(),以保证宏展开一定不会出错

4. 带参宏 与 函数

这两个玩意儿长得很像,但实际上是两个完全不同的东西。

1)例子
#include <stdio.h>

#define  S(a,b)  a*b*10

void s(int a, int b)
{
    return a*b*10;
}


int main(void)
{
    int va1, va2;
    va1 = S(3, 2); //引用带参宏
    va2 = s(3, 2); //调用函数
    printf("va1 = %d, va2 = %d\n", va1, va2);
    return 0;
}

仅仅从调用来看,这两个东西确实长得很像,如果将宏也定义为小写的话,
仅看调用的话,很难看出这个到底谁是函数谁宏定义

为了能够让大家快速的区分带参宏和函数,大家在定义宏的时候,宏名一定要大写,否则在阅读代码时,
很容易与函数搞混,非常不利于代码的阅读和理解

2)二者的区别

二者是有着本质区别的:

(a)带参宏
处理阶段:预编译

宏只是一个供我们程序员识别的一个符号,一旦预编译之后带参宏就会消失了,取而代之的是宏体

参数列表

带参宏的形参是没有类型的,我们使用int 、float等类型只有一个目的,就是使用类型来开辟一个变量空间,
变量空间的字节数和存储格式是由类型来决定的,所以定义变量时必须要有类型说明

而带参宏的参数仅仅只起到替换说明的作用,不需要开辟空间来存放实参的值,
既然不需要开辟空间,那就不需要类型的说明

(b)函数
处理阶段:由编译、汇编、链接阶段处理

在“预编译阶段”是不会处理函数这个东西的,在预编译前后,函数没有任何变化

  • 编译:将C形式的函数编译为汇编形式的函数
  • 汇编:将汇编形式的函数,转为二进制指令的函数
  • 链接:进行符号统一和重定位

函数是一个独立体,有调用的过程,运行函数时涉及调用的过程

  • 调用时:从当前函数跳转到被调用的函数,开辟形参和自动局部变量时,涉及压栈操作
  • 调用结束:返回到调用函数,释放函数的形参和自动局部变量的空间时,涉及弹栈操作

函数的参数列表

函数的形参是需要被开辟空间的,所以必须要要有类型说明