一、宏讲解
1、宏定义
宏(Macro),是一种批量处理的称谓。计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。
2、C语言宏定义的常规用法
1) 定义符号常量
#define PI 3.1415926
#define MAX_N 10000
2) 定义傻瓜表达式(注意,定义的这种表达式一不小心很容易出现bug,下文会讲)
#define S(a, b) a * b
#define MAX(a, b) (a) > (b) ? (a) : (b)
3) 定义代码段
#define P(a) {
printf("%d
", a);
}
ps:编译器对于宏的解析是很严谨的,只能支持一行解析,是起连接作用,表示当行的宏代码与下一行宏连接在一起,使得编译器当成一行看待。
3、编译器预定义的宏
在C语言中,我们有很多预定义的宏,就是C语言帮程序员预先定义好的宏,可以让我们使用。
宏 说明
__DATE__ 日期:Mmm dd yyyy
__TIME__ 时间:hh:mm:ss
__LINE__ 当前源文件的代码行号
__FILE__ 文件名
__func__ 函数名/非标准
__FUNC__ 函数名/非标准
__PRETTY_FUNCTION__ 更详细的函数信息/非标准
4、预定义命令-条件式编译
函数 说明
#ifdef DEBUG 是否定义了DEBUG宏
#ifndef DEBUG 是否没有定义DEBUG宏
#if MAX_N == 5 宏MAX_N是否等于5
#elif MAX_N == 4 否则宏MAX_N是否等于4
#else
#endif
5、预定义命令
从上图可以看到:
预编译
将.c 文件转化成 .i文件
使用的gcc命令是:gcc –E
对应于预处理命令cpp
编译
将.c/.h文件转换成.s文件
使用的gcc命令是:gcc –S
对应于编译命令 cc –S
汇编
将.s 文件转化成 .o文件
使用的gcc 命令是:gcc –c
对应于汇编命令是 as
链接
将.o文件转化成可执行程序
使用的gcc 命令是: gcc
对应于链接命令是 ld
总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。这里我们主要讲预编译阶段,不去细究其他阶段,具体细节可以去看编译原理的书本。
而我们这里只讲预定义,也就是说,我们可以通过预编译生成的编译源码去看我们的宏替换后有没有符合我们的预期,下面会实际操作。
二、宏使用
上文,我们知道了宏的三种用法,分别如下:
1) 定义符号常量
2) 定义傻瓜表达式
3) 定义代码段
例子一:
定义一个宏,表示一年有多少秒?
//seconds.c
#include <stdio.h>
#define SEC_OF_A_YEAR (365 * 24 * 60 * 60)
int main(void) {
printf("%d
", SEC_OF_A_YEAR);
return 0;
}
我们对源码进行预编译操作,顺便去查看预编译后的结果,如下
ydq@ubuntu:macro$ gcc -E seconds.c > seconds.txt
ydq@ubuntu:macro$ tail seconds.txt
# 2 "seconds.c" 2
# 4 "seconds.c"
int main(void) {
printf("%d
", (365 * 24 * 60 * 60));
return 0;
}
ydq@ubuntu:macro$
可以看到预编译后,我们的宏SEC_OF_YEAR被替换成了(365 * 24 * 60 * 60),所以我们可以得知,宏定义在预编译后只是进行了简单的代码替换,这其实对于新手来说,是很危险的。
例子二:
请定义一个没有bug的MAX(a, b)宏,需要通过如下测试:
1、MAX(2, 3)
2、5 + MAX(2, 3);
3、MAX(2, MAX(3, 4))
4、MAX(2, 3 > 4 ? 3 : 4)
5、MAX(a++, 6)a的初值为7,宏返回值为7,a的值变为8。
验证1:
1 //max_version1.c
2 #include <stdio.h>
3 #define MAX(a, b) a > b ? a : b
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 return 0;
8 }
ydq@ubuntu:macro$ gcc max_version1.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
由结果得出验证通过,那么我们接着下来在该代码基础上,继续验证2.
1 //max_version1.c
2 #include <stdio.h>
3 #define MAX(a, b) a > b ? a : b
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
8 return 0;
9 }
ydq@ubuntu:macro$ gcc max_version1.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 2
这时候我们发现验证2的时候结果错了,我们预编译出结果来看一下是怎么回事。
dq@ubuntu:macro$ gcc -E max_version1.c > max_version1.txt
ydq@ubuntu:macro$ tail max_version1.txt
# 2 "max_version1.c" 2
# 4 "max_version1.c"
int main(void) {
printf("MAX(%d, %d) = %d
", 2, 3, 2 > 3 ? 2 : 3);
printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + 2 > 3 ? 2 : 3);
return 0;
}
ydq@ubuntu:macro$
由汇编源码看出5 + MAX(2, 3)被替换为5 + 2 > 3 ? 2 : 3,由于+运算符的优先级比>号运算符的优先级高,又是左结合,所以5 + 2 > 3 ? 2 : 3相当于是7 > 3 ? 2 : 3,故最终输出结果是为2。为了解决这个bug,我们可以通过把三目运算符的整个表达式给用括号括起来,让其优先级最高,如#define MAX(a, b) (a > b ? a : b),代码如下:
1 //max_version2.c
2 #include <stdio.h>
3 #define MAX(a, b) (a > b ? a : b)
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
8 return 0;
9 }
接着,编译测试:
ydq@ubuntu:macro$ gcc max_version2.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
现在,我们得出结果正确了。接着我们可以继续添加代码验证3。
1 //max_version2.c
2 #include <stdio.h>
3 #define MAX(a, b) (a > b ? a : b)
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
8 printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, MAX(2, MAX(3, 4)));
9 return 0;
10 }
编译验证结果:
ydq@ubuntu:macro$ gcc max_version2.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
我们发现,题目3也是正确的。那我们接着用这个版本的代码继续验证题目4。
1 //max_version2.c
2 #include <stdio.h>
3 #define MAX(a, b) (a > b ? a : b)
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
8 printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, MAX(2, MAX(3, 4)));
9 printf("MAX(%d, %d > %d ? %d : %d) = %d
", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
10 return 0;
11 }
编译测试结果
ydq@ubuntu:macro$ gcc max_version2.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 2
发现结果错了,理论上应该是4,结果却是2。继续用预编译命令验证。
ydq@ubuntu:macro$ gcc max_version2.c -E > max_version2.txt
ydq@ubuntu:macro$ tail max_version2.txt
# 5 "max_version2.c"
int main(void) {
printf("MAX(%d, %d) = %d
", 2, 3, (2 > 3 ? 2 : 3));
printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + (2 > 3 ? 2 : 3));
printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, (2 > (3 > 4 ? 3 : 4) ? 2 : (3 > 4 ? 3 : 4)));
printf("MAX(%d, %d > %d ? %d : %d) = %d
", 2, 3, 4, 3, 4, (2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4));
return 0;
}
ydq@ubuntu:macro$
我们MAX(2, 3 > 4 ? 3 : 4)被替换成(2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4),因为2 > 3 > 4 的结果为0,所以2 > 3 > 4 ? 3 : 4为4,4为真,(2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4)就为2,故打印出来的结果是为2。由此我们得出,一旦a和b是表达式的话,MAX(a, b)会有可能有bug,我们还是得用括号保护a和b,a和b若是表达式时能够优先级最大。下面我们给出代码
1 //max_version2.c
2 #include <stdio.h>
3 #define MAX(a, b) ((a) > (b) ? (a) : (b))
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
8 printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, MAX(2, MAX(3, 4)));
9 printf("MAX(%d, %d > %d ? %d : %d) = %d
", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
10 return 0;
11 }
编译并运行验证。
ydq@ubuntu:macro$ gcc max_version3.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
接下来我们验证最后的一个题目。
1 //max_version2.c
2 #include <stdio.h>
3 #define MAX(a, b) ((a) > (b) ? (a) : (b))
4
5 int main(void) {
6 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
7 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
8 printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, MAX(2, MAX(3, 4)));
9 printf("MAX(%d, %d > %d ? %d : %d) = %d
", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
10 int a = 7;
11 printf("MAX(a++, %d) = %d
", 6, MAX(a++, 6));
12 printf("a = %d
", a);
13 return 0;
14 }
编译并运行验证
ydq@ubuntu:macro$ gcc max_version3.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 6) = 8
a = 9
原题要求 MAX(a++, 6)a的初值为7,宏返回值为7,a的值变为8。
但实际结果是宏的返回值时8,a的值变为了9。
这里我们继续用预编译去验证。
ydq@ubuntu:macro$ gcc -E max_version3.c > max_version3.txt
ydq@ubuntu:macro$ tail max_version3.txt
int main(void) {
printf("MAX(%d, %d) = %d
", 2, 3, ((2) > (3) ? (2) : (3)));
printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + ((2) > (3) ? (2) : (3)));
printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, ((2) > (((3) > (4) ? (3) : (4))) ? (2) : (((3) > (4) ? (3) : (4)))));
printf("MAX(%d, %d > %d ? %d : %d) = %d
", 2, 3, 4, 3, 4, ((2) > (3 > 4 ? 3 : 4) ? (2) : (3 > 4 ? 3 : 4)));
int a = 7;
printf("MAX(a++, %d) = %d
", 6, ((a++) > (6) ? (a++) : (6)));
printf("a = %d
", a);
return 0;
}
解决方法如下:
1 //max_version4.c
2 #include <stdio.h>
3 #define MAX(a, b) ({
4 __typeof(a) _a = (a);
5 __typeof(b) _b = (b);
6 _a > _b? _a : _b;
7 })
8
9 int main(void) {
10 printf("MAX(%d, %d) = %d
", 2, 3, MAX(2, 3));
11 printf("%d + MAX(%d, %d) = %d
", 5, 2, 3, 5 + MAX(2, 3));
12 printf("MAX(%d, MAX(%d, %d)) = %d
", 2, 3, 4, MAX(2, MAX(3, 4)));
13 printf("MAX(%d, %d > %d ? %d : %d) = %d
", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
14 int a = 7;
15 printf("MAX(a++, %d) = %d
", 6, MAX(a++, 6));
16 printf("a = %d
", a);
17 return 0;
18 }
编译并运行测试
ydq@ubuntu:macro$ gcc max_version4.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 6) = 7
a = 8
至此,我们已经测试并通过上述5个测试的MAX(a, b)宏,由此可见,运用宏去定义表达式要格外小心。
例子三,我们来实现一个打印宏P(func),实现调用P(MAX(2, 3))可以打印出MAX(2, 3) = 3。
1 //max_version5.c
2 #include <stdio.h>
3 #define MAX(a, b) ({
4 __typeof(a) _a = (a);
5 __typeof(b) _b = (b);
6 _a > _b? _a : _b;
7 })
8
9 #define P(func) {
10 printf("%s = %d
", #func, func);
11 }
12
13 int main(void) {
14 P(MAX(2, 3));
15 P(5 + MAX(2, 3));
16 P(MAX(2, MAX(3, 4)));
17 P(MAX(2, 3 > 4 ? 3 : 4));
18 int a = 7;
19 P(MAX(a++, 6));
20 P(a);
21 return 0;
22 }
#func表示将func字符串化,编译运行。
ydq@ubuntu:macro$ gcc max_version5.c
ydq@ubuntu:macro$ ./a.out
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 6) = 7
a = 8
例子4:实现一个打印LOG的函数,需要输出所在函数及行号等信息
注:宏__FILE__以字符串形式返回所在文件名称
宏__func__以字符串形式返回所在函数名称
宏__LINE__以整数形式返回代码行号
1 #include <stdio.h> 2 3 #ifdef DEBUG 4 #define LOG(frm, args...) { 5 printf("[%s : %s : %d] ", __FILE__, __func__, __LINE__); 6 printf(frm, ##args); 7 printf(" "); 8 } 9 #else 10 #define LOG(frm, args...) { } 11 #endif 12 13 int main(void) { 14 int a = 123; 15 int b = 456; 16 printf("[%s : %s : %d] %d ", __FILE__, __func__, __LINE__, a); 17 LOG("a = %d, b = %d", a, b); 18 LOG("hello world!"); 19 return 0; 20 }
编译并运行。
ydq@ubuntu:macro$ gcc log.c -DDEBUG ydq@ubuntu:macro$ ./a.out [log.c : main : 16] 123 [log.c : main : 17] a = 123, b = 456 [log.c : main : 18] hello world!