一、预处理完之后,宏定义和宏引用都会消失

#define NUM  100  //宏定义,预处理后消失

int main
{
    int a;
    a = NUM;  //宏引用,预处理后被替换为宏体,宏引用消失
    return 0;
}

二、宏名的字母一般习惯大写

有关宏名大写的习惯并不是没事找事,如果宏名小写的话,会很容易和正常的变量和函数混淆

(1)小写的无参宏很容易和变量混淆

#define pi 3.14
int a = 10;
int main(void)
{
    int c = a + pi;
    return 0;
}

如果pi不大写的话,仅看使用的位置,很难判断出pi是宏还是变量。

(2)小写的带参数宏很容易和函数混淆

#define area(a, b)  ((a)*(b))
int main(void)
{
    int c = area(10, 20); //仅看调用的位置,很难判断area是一个宏还是一个函数。
    return 0;
}

(3)宏名与变量和函数混淆到底有什么不好

如果你把宏和变量函数都定义成小写的话,阅读宏时就很容易误解为变量和函数,
但是实际上它们之间是有本质区别的,这种理解的错误就非常容易给我们代码阅读带来很多的困惑

正是基于这样的道理,我们这里要求大家尽量将宏写成大写的,而且一定要见名识意

疑问:难道真的没有小写的宏吗?
其实也不是,在少数某些特殊情况下,还真有定义为小写的,但是这种情况比较少见

比如大家在学习标准IO函数时,有三个宏:

通过 gcc-v 选项找出这三个宏的位置在 /usr/include/stdio.h168 行左右

#include "..." search starts here: # 包含""所指定的头文件:到程序员自己指定的路径下去搜索
#include <...> search starts here: # 包含<>所指定的头文件:到系统指定的路径下去找
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include
 /usr/local/include
 /usr/include
End of search list.

查看这个文件,发现是先定义了结构体指针变量,又定义了一个宏来替换这个指针变量

$ vim /usr/include/stdio.h
/* Standard streams.  */  
extern struct _IO_FILE *stdin;          /* Standard input stream.  */  
extern struct _IO_FILE *stdout;         /* Standard output stream.  */  
extern struct _IO_FILE *stderr;         /* Standard error output stream.  */  
/* C89/C99 say they're macros.  Make them happy.  */  
#define stdin stdin
#define stdout stdout
#define stderr stderr
stdin:标准输入(从键盘输入数据)
stdout:标准输出
stderr:标注出错输出

这三个宏其实就是小写的,之所以写成小写,应该是历史遗留问题,
除了少数的特例之外,对于宏来说,我们要都要大写


三、宏定义不是C语句,不必在行末加封号

这个在前面就介绍过,这里我们介绍一个特殊例子:

#define STUDENT struct student{int a; int b;};

例子中的分号只是宏体struct student{int a; int b;};的一个组成部分而已,并不是说封号结尾

四、所有预编译的代码都是独占一行的(不能多行)

#define STUDENT struct student{int a; int b;};

为了独占一行,我把结构体写在了一行中,但是这样子不方便理解,我们往往会把它改成如下形式

#define STUDENT struct student{\
int a; \
int b;\
};

加了\(连行符)后,其实这几行在同一行中

五、宏的作用域 与 #undef

正常情况下的宏作用域为从定义为位置开始,一直到文件的末尾,
如果你希望结束宏的作用域的话,可以使用#undef这个预编译关键字

#define NUM 100 

int fun();

int main(void)
{
    int a = NUM;
    return 0;
}

#undef NUM  // NUM这个宏的作用域,到这里就结束了

int fun()
{
    int a = NUM;//这里将无法使用这个宏
}

这里虽然是以.c文件来举的例子,但是如果将宏定义在.h中时,它作用域以及#undef的用法也是一样的

对于#undef这个预编译关键字来说,在我们自己的代码中用的比较少,
但是大家在阅读OS、库、框架等复杂C源码时,不时的还是会见到这个关键字


六、 定义宏时可以嵌套引用其它的宏,但是不能嵌套引用自己

1. 嵌套其它宏

#define   WIDTH   80
#define   LENGTH   (WIDTH)+40
#define   AREAWIDTH*(LENGTH)
int main(void)
{
    int a = AREA;
    return 0;
}

请问:如下写法正确吗?

#define   AREA    WIDTH*(LENGTH)
#define   WIDTH   80
#define   LENGTH  (WIDTH)+40

int main(void)
{
    int a = AREA;
    return 0;
}

这个写法是正确的,只要宏引用的位置在定义位置的作用域范围内就行

显然AREA的引用都在AREAWIDTHLENGTH作用域内,
所以AREA的引用在替换时,完全不存在任何问题

int a = AREA ——> int a = WIDTH*(LENGTH) ——> int a = 80*(80+40)

所以只要宏引用的位置都在“宏定义”的作用域范围内,对于有嵌套关系的几个宏来说,它们的先后关系不存在任何
影响

2. 为什么不能嵌套自己

#define  AREA  AREA*10

int main(void)
{
    int a = AREA;
    return 0;
}

进行宏替换:

int a = AREA ————> int a = AREA * 10

从这个例子可以看出,嵌套自己时在预编译器做完替换后,最后还剩一个宏名,
这个宏名无法再被替换,最后留给第二阶段编译时,将变成一个无法识别的符号,从而报错

所以宏不能嵌套自己,这个函数不一样,函数嵌套调用自己是递归,宏嵌套引用自己就是犯错


七、只作字符替换,不做正确性检查

预编译器处理宏时,预编译器只关心替换的事情,至于替换的宏体的写法是否正确,
预编译器本身并不做检查,因为判断写法是否正确的这件事情是由第二阶段的编译来做的

例子

#define NUM 100WEE

int main(void)
{
    int a = NUM;
    return 0;
}

例子中整形数100WEE的写法完全是错的,但是在预编译时根本不会提示任何错误,
预编译器会忠实的将NUM换为100WEE
演示:

gcc -E a.c -o a.i  //仅预编译

但是后续编译时就会报无法识别100WEE的错误。
演示:

gcc -S a.i -o a.s